Merge "Remove TODO"
diff --git a/Android.bp b/Android.bp
index 8853694..0547481 100644
--- a/Android.bp
+++ b/Android.bp
@@ -413,6 +413,8 @@
         "framework-platform-compat-config",
         "libcore-platform-compat-config",
         "services-platform-compat-config",
+        "media-provider-platform-compat-config",
+        "services-devicepolicy-platform-compat-config",
     ],
     sdk_version: "core_platform",
 }
@@ -1460,6 +1462,11 @@
             removed_api_file: "api/removed.txt",
             baseline_file: ":public-api-incompatibilities-with-last-released",
         },
+        api_lint: {
+            enabled: true,
+            new_since: ":last-released-public-api",
+            baseline_file: "api/lint-baseline.txt",
+        },
     },
     jdiff_enabled: true,
 }
@@ -1486,6 +1493,11 @@
             removed_api_file: "api/system-removed.txt",
             baseline_file: ":system-api-incompatibilities-with-last-released"
         },
+        api_lint: {
+            enabled: true,
+            new_since: ":last-released-system-api",
+            baseline_file: "api/system-lint-baseline.txt",
+        },
     },
     jdiff_enabled: true,
 }
@@ -1584,4 +1596,4 @@
         "core/java/com/android/internal/util/State.java",
         "core/java/com/android/internal/util/StateMachine.java",
     ],
-}
\ No newline at end of file
+}
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java b/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
index 6979f0f..48ce8ab 100644
--- a/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
@@ -20,9 +20,9 @@
 
 import android.os.Looper;
 import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.PerfTestActivity;
 import android.perftests.utils.SettingsHelper;
 import android.perftests.utils.SettingsStateKeeperRule;
-import android.perftests.utils.StubActivity;
 import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
@@ -46,8 +46,8 @@
                     Settings.Secure.AUTOFILL_SERVICE);
 
     @Rule
-    public ActivityTestRule<StubActivity> mActivityRule =
-            new ActivityTestRule<StubActivity>(StubActivity.class);
+    public ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -68,7 +68,7 @@
                     Looper.getMainLooper().getThread() == Thread.currentThread());
             assertTrue("We should be running on the main thread",
                     Looper.myLooper() == Looper.getMainLooper());
-            StubActivity activity = mActivityRule.getActivity();
+            PerfTestActivity activity = mActivityRule.getActivity();
             activity.setContentView(mLayoutId);
             onCreate(activity);
         });
@@ -89,9 +89,9 @@
     }
 
     /**
-     * Initializes the {@link StubActivity} after it was launched.
+     * Initializes the {@link PerfTestActivity} after it was launched.
      */
-    protected abstract void onCreate(StubActivity activity);
+    protected abstract void onCreate(PerfTestActivity activity);
 
     /**
      * Uses the {@code settings} binary to set the autofill service.
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java b/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
index 8090826..fb5ea80 100644
--- a/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
@@ -20,7 +20,7 @@
 import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
 
 import android.perftests.utils.BenchmarkState;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
 import android.view.View;
 import android.widget.EditText;
 
@@ -39,7 +39,7 @@
     }
 
     @Override
-    protected void onCreate(StubActivity activity) {
+    protected void onCreate(PerfTestActivity activity) {
         View root = activity.getWindow().getDecorView();
         mUsername = root.findViewById(R.id.username);
         mPassword = root.findViewById(R.id.password);
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index 525975d..290f178 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -13,7 +13,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/app/PendingIntentPerfTest.java b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
index b3f8359..a320514 100644
--- a/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
+++ b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
@@ -20,7 +20,7 @@
 import android.content.Intent;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
@@ -48,7 +48,7 @@
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getTargetContext();
-        mIntent = StubActivity.createLaunchIntent(mContext);
+        mIntent = PerfTestActivity.createLaunchIntent(mContext);
     }
 
     /**
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java
index 3a80020..b9c7af4 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java
@@ -19,7 +19,7 @@
 import android.graphics.Paint;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.rule.ActivityTestRule;
@@ -58,7 +58,8 @@
     }
 
     @Rule
-    public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+    public ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
index 3b2b8a9..d14e93e 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
@@ -26,7 +26,7 @@
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.BitmapUtils;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
 import android.test.suitebuilder.annotation.LargeTest;
 
 import androidx.test.rule.ActivityTestRule;
@@ -48,8 +48,8 @@
     private int[] mTestHeights = {512, 1024};
 
     @Rule
-    public ActivityTestRule<StubActivity> mActivityRule =
-            new ActivityTestRule(StubActivity.class);
+    public ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
index 5be99d9..6b295e5 100644
--- a/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
@@ -23,7 +23,7 @@
 import android.graphics.Paint.FontMetricsInt;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
 import android.text.style.ReplacementSpan;
 import android.util.ArraySet;
 
@@ -75,7 +75,8 @@
     }
 
     @Rule
-    public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+    public ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
index b34001d..b0edb11 100644
--- a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
@@ -23,7 +23,7 @@
 import android.graphics.drawable.ColorDrawable;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
 import android.view.View.MeasureSpec;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
@@ -46,7 +46,8 @@
 public class ViewShowHidePerfTest {
 
     @Rule
-    public ActivityTestRule mActivityRule = new ActivityTestRule(StubActivity.class);
+    public ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
index b3ea62a..270b4e5 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
@@ -18,7 +18,7 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
 import android.text.Selection;
 import android.view.KeyEvent;
 import android.view.View.MeasureSpec;
@@ -80,7 +80,8 @@
     }
 
     @Rule
-    public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+    public ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
index aa47d5b..8028f11 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
@@ -18,7 +18,7 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
 import android.text.Selection;
 import android.view.KeyEvent;
 import android.view.View.MeasureSpec;
@@ -74,7 +74,8 @@
     }
 
     @Rule
-    public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+    public ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
index e50016c..f4ad5dd 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
@@ -19,7 +19,7 @@
 import android.app.Activity;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
 import android.view.KeyEvent;
 import android.view.View.MeasureSpec;
 import android.view.ViewGroup;
@@ -59,7 +59,8 @@
     }
 
     @Rule
-    public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+    public ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java b/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
index 644095b..223a316 100644
--- a/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
@@ -28,7 +28,7 @@
 import android.os.Looper;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -72,8 +72,8 @@
     }
 
     @Rule
-    public ActivityTestRule<StubActivity> mActivityRule =
-            new ActivityTestRule(StubActivity.class);
+    public ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule(PerfTestActivity.class);
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java b/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java
index bed173b..694e1f4 100644
--- a/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java
@@ -22,7 +22,7 @@
 import android.os.Looper;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.rule.ActivityTestRule;
@@ -64,8 +64,8 @@
     }
 
     @Rule
-    public ActivityTestRule<StubActivity> mActivityRule =
-            new ActivityTestRule(StubActivity.class);
+    public ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule(PerfTestActivity.class);
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java b/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
index 00bd8db..a546667 100644
--- a/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
@@ -18,7 +18,7 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.rule.ActivityTestRule;
@@ -56,7 +56,8 @@
     }
 
     @Rule
-    public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+    public ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
 
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
new file mode 100644
index 0000000..c096cd2
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.wm;
+
+import static android.perftests.utils.ManualBenchmarkState.StatsReport;
+
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.perftests.utils.TraceMarkParser;
+import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.lifecycle.Stage;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.concurrent.TimeUnit;
+
+/** Measure the performance of internal methods in window manager service by trace tag. */
+@LargeTest
+public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase {
+    private static final String TAG = InternalWindowOperationPerfTest.class.getSimpleName();
+
+    @Rule
+    public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
+
+    @Rule
+    public final PerfTestActivityRule mActivityRule = new PerfTestActivityRule();
+
+    private final TraceMarkParser mTraceMarkParser = new TraceMarkParser(
+            "applyPostLayoutPolicy",
+            "applySurfaceChanges",
+            "AppTransitionReady",
+            "closeSurfaceTransactiom",
+            "openSurfaceTransaction",
+            "performLayout",
+            "performSurfacePlacement",
+            "prepareSurfaces",
+            "updateInputWindows",
+            "WSA#startAnimation",
+            "activityIdle",
+            "activityPaused",
+            "activityStopped",
+            "activityDestroyed",
+            "finishActivity",
+            "startActivityInner");
+
+    @Test
+    @ManualBenchmarkTest(
+            targetTestDurationNs = 20 * TIME_1_S_IN_NS,
+            statsReport = @StatsReport(
+                    flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
+                            | StatsReport.FLAG_MAX | StatsReport.FLAG_COEFFICIENT_VAR))
+    public void testLaunchAndFinishActivity() throws Throwable {
+        final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        long measuredTimeNs = 0;
+        boolean isTraceStarted = false;
+
+        while (state.keepRunning(measuredTimeNs)) {
+            if (!isTraceStarted && !state.isWarmingUp()) {
+                startAsyncAtrace();
+                isTraceStarted = true;
+            }
+            final long startTime = SystemClock.elapsedRealtimeNanos();
+            mActivityRule.launchActivity();
+            mActivityRule.finishActivity();
+            mActivityRule.waitForIdleSync(Stage.DESTROYED);
+            measuredTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
+        }
+
+        stopAsyncAtrace();
+
+        mTraceMarkParser.forAllSlices((key, slices) -> {
+            for (TraceMarkSlice slice : slices) {
+                state.addExtraResult(key, (long) (slice.getDurarionInSeconds() * NANOS_PER_S));
+            }
+        });
+
+        Log.i(TAG, String.valueOf(mTraceMarkParser));
+    }
+
+    private void startAsyncAtrace() throws IOException {
+        sUiAutomation.executeShellCommand("atrace -b 32768 --async_start wm");
+        // Avoid atrace isn't ready immediately.
+        SystemClock.sleep(TimeUnit.NANOSECONDS.toMillis(TIME_1_S_IN_NS));
+    }
+
+    private void stopAsyncAtrace() throws IOException {
+        final ParcelFileDescriptor pfd = sUiAutomation.executeShellCommand("atrace --async_stop");
+        final InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                mTraceMarkParser.visit(line);
+            }
+        }
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
index 9cfc3d2..73b4a19 100644
--- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
@@ -16,16 +16,13 @@
 
 package android.wm;
 
-import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_COEFFICIENT_VAR;
-import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_ITERATION;
-import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_MEAN;
+import static android.perftests.utils.ManualBenchmarkState.StatsReport;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.hamcrest.core.AnyOf.anyOf;
 import static org.hamcrest.core.Is.is;
 
-import android.app.Activity;
 import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
@@ -39,23 +36,16 @@
 import android.perftests.utils.ManualBenchmarkState;
 import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
 import android.perftests.utils.PerfManualStatusReporter;
-import android.perftests.utils.StubActivity;
 import android.util.Pair;
 import android.view.IRecentsAnimationController;
 import android.view.IRecentsAnimationRunner;
 import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
 
 import androidx.test.filters.LargeTest;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
-import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
 import androidx.test.runner.lifecycle.Stage;
 
-import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Assume;
-import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
@@ -77,11 +67,10 @@
     public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
 
     @Rule
-    public final ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule<>(
-            StubActivity.class, false /* initialTouchMode */, false /* launchActivity */);
+    public final PerfTestActivityRule mActivityRule =
+            new PerfTestActivityRule(true /* launchActivity */);
 
     private long mMeasuredTimeNs;
-    private LifecycleListener mLifecycleListener;
 
     @Parameterized.Parameter(0)
     public int intervalBetweenOperations;
@@ -127,24 +116,6 @@
         sUiAutomation.dropShellPermissionIdentity();
     }
 
-    @Before
-    @Override
-    public void setUp() {
-        super.setUp();
-        final Activity testActivity = mActivityRule.launchActivity(null /* intent */);
-        try {
-            mActivityRule.runOnUiThread(() -> testActivity.getWindow()
-                    .addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
-        } catch (Throwable ignored) { }
-        mLifecycleListener = new LifecycleListener(testActivity);
-        ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(mLifecycleListener);
-    }
-
-    @After
-    public void tearDown() {
-        ActivityLifecycleMonitorRegistry.getInstance().removeLifecycleCallback(mLifecycleListener);
-    }
-
     /** Simulate the timing of touch. */
     private void makeInterval() {
         SystemClock.sleep(intervalBetweenOperations);
@@ -167,8 +138,8 @@
     @ManualBenchmarkTest(
             warmupDurationNs = TIME_1_S_IN_NS,
             targetTestDurationNs = TIME_5_S_IN_NS,
-            statsReportFlags =
-                    STATS_REPORT_ITERATION | STATS_REPORT_MEAN | STATS_REPORT_COEFFICIENT_VAR)
+            statsReport = @StatsReport(flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
+                    | StatsReport.FLAG_COEFFICIENT_VAR))
     public void testRecentsAnimation() throws Throwable {
         final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         final IActivityTaskManager atm = ActivityTaskManager.getService();
@@ -201,7 +172,7 @@
                 state.addExtraResult(finishCase.first, elapsedTimeNsOfFinish);
 
                 if (moveRecentsToTop) {
-                    mLifecycleListener.waitForIdleSync(Stage.STOPPED);
+                    mActivityRule.waitForIdleSync(Stage.STOPPED);
 
                     startTime = SystemClock.elapsedRealtimeNanos();
                     atm.startActivityFromRecents(testActivityTaskId, null /* options */);
@@ -209,7 +180,7 @@
                     mMeasuredTimeNs += elapsedTimeNs;
                     state.addExtraResult("startFromRecents", elapsedTimeNs);
 
-                    mLifecycleListener.waitForIdleSync(Stage.RESUMED);
+                    mActivityRule.waitForIdleSync(Stage.RESUMED);
                 }
 
                 makeInterval();
@@ -223,55 +194,18 @@
             }
         };
 
+        recentsSemaphore.tryAcquire();
         while (state.keepRunning(mMeasuredTimeNs)) {
-            Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS));
+            mMeasuredTimeNs = 0;
 
             final long startTime = SystemClock.elapsedRealtimeNanos();
             atm.startRecentsActivity(sRecentsIntent, null /* unused */, anim);
             final long elapsedTimeNsOfStart = SystemClock.elapsedRealtimeNanos() - startTime;
             mMeasuredTimeNs += elapsedTimeNsOfStart;
             state.addExtraResult("start", elapsedTimeNsOfStart);
-        }
 
-        // Ensure the last round of animation callback is done.
-        recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS);
-        recentsSemaphore.release();
-    }
-
-    private static class LifecycleListener implements ActivityLifecycleCallback {
-        private final Activity mTargetActivity;
-        private Stage mWaitingStage;
-        private Stage mReceivedStage;
-
-        LifecycleListener(Activity activity) {
-            mTargetActivity = activity;
-        }
-
-        void waitForIdleSync(Stage state) {
-            synchronized (this) {
-                if (state != mReceivedStage) {
-                    mWaitingStage = state;
-                    try {
-                        wait(TimeUnit.NANOSECONDS.toMillis(TIME_5_S_IN_NS));
-                    } catch (InterruptedException impossible) { }
-                }
-                mWaitingStage = mReceivedStage = null;
-            }
-            getInstrumentation().waitForIdleSync();
-        }
-
-        @Override
-        public void onActivityLifecycleChanged(Activity activity, Stage stage) {
-            if (mTargetActivity != activity) {
-                return;
-            }
-
-            synchronized (this) {
-                mReceivedStage = stage;
-                if (mWaitingStage == mReceivedStage) {
-                    notifyAll();
-                }
-            }
+            // Ensure the animation callback is done.
+            Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS));
         }
     }
 }
diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
index f0c474b..f43bdf8 100644
--- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
@@ -24,7 +24,7 @@
 import android.os.RemoteException;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
 import android.util.MergedConfiguration;
 import android.view.DisplayCutout;
 import android.view.IWindow;
@@ -57,8 +57,8 @@
     public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
     @Rule
-    public final ActivityTestRule<StubActivity> mActivityRule =
-            new ActivityTestRule<>(StubActivity.class);
+    public final ActivityTestRule<PerfTestActivity> mActivityRule =
+            new ActivityTestRule<>(PerfTestActivity.class);
 
     /** This is only a placement to match the input parameters from {@link #getParameters}. */
     @Parameterized.Parameter(0)
diff --git a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
index 4864da4..4d278c3 100644
--- a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
+++ b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
@@ -18,9 +18,21 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import android.app.Activity;
 import android.app.UiAutomation;
+import android.content.Intent;
+import android.perftests.utils.PerfTestActivity;
 
-import org.junit.Before;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
+import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
+import androidx.test.runner.lifecycle.Stage;
+
+import org.junit.BeforeClass;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.concurrent.TimeUnit;
 
 public class WindowManagerPerfTestBase {
     static final UiAutomation sUiAutomation = getInstrumentation().getUiAutomation();
@@ -28,10 +40,102 @@
     static final long TIME_1_S_IN_NS = 1 * NANOS_PER_S;
     static final long TIME_5_S_IN_NS = 5 * NANOS_PER_S;
 
-    @Before
-    public void setUp() {
+    @BeforeClass
+    public static void setUpOnce() {
         // In order to be closer to the real use case.
         sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP");
         sUiAutomation.executeShellCommand("wm dismiss-keyguard");
+        getInstrumentation().getContext().startActivity(new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+    }
+
+    /**
+     * Provides an activity that keeps screen on and is able to wait for a stable lifecycle stage.
+     */
+    static class PerfTestActivityRule extends ActivityTestRule<PerfTestActivity> {
+        private final Intent mStartIntent =
+                new Intent().putExtra(PerfTestActivity.INTENT_EXTRA_KEEP_SCREEN_ON, true);
+        private final LifecycleListener mLifecycleListener = new LifecycleListener();
+
+        PerfTestActivityRule() {
+            this(false /* launchActivity */);
+        }
+
+        PerfTestActivityRule(boolean launchActivity) {
+            super(PerfTestActivity.class, false /* initialTouchMode */, launchActivity);
+        }
+
+        @Override
+        public Statement apply(Statement base, Description description) {
+            final Statement wrappedStatement = new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    ActivityLifecycleMonitorRegistry.getInstance()
+                            .addLifecycleCallback(mLifecycleListener);
+                    base.evaluate();
+                    ActivityLifecycleMonitorRegistry.getInstance()
+                            .removeLifecycleCallback(mLifecycleListener);
+                }
+            };
+            return super.apply(wrappedStatement, description);
+        }
+
+        @Override
+        protected Intent getActivityIntent() {
+            return mStartIntent;
+        }
+
+        @Override
+        public PerfTestActivity launchActivity(Intent intent) {
+            final PerfTestActivity activity = super.launchActivity(intent);
+            mLifecycleListener.setTargetActivity(activity);
+            return activity;
+        }
+
+        PerfTestActivity launchActivity() {
+            return launchActivity(mStartIntent);
+        }
+
+        void waitForIdleSync(Stage state) {
+            mLifecycleListener.waitForIdleSync(state);
+        }
+    }
+
+    static class LifecycleListener implements ActivityLifecycleCallback {
+        private Activity mTargetActivity;
+        private Stage mWaitingStage;
+        private Stage mReceivedStage;
+
+        void setTargetActivity(Activity activity) {
+            mTargetActivity = activity;
+            mReceivedStage = mWaitingStage = null;
+        }
+
+        void waitForIdleSync(Stage stage) {
+            synchronized (this) {
+                if (stage != mReceivedStage) {
+                    mWaitingStage = stage;
+                    try {
+                        wait(TimeUnit.NANOSECONDS.toMillis(TIME_5_S_IN_NS));
+                    } catch (InterruptedException impossible) { }
+                }
+                mWaitingStage = mReceivedStage = null;
+            }
+            getInstrumentation().waitForIdleSync();
+        }
+
+        @Override
+        public void onActivityLifecycleChanged(Activity activity, Stage stage) {
+            if (mTargetActivity != activity) {
+                return;
+            }
+
+            synchronized (this) {
+                mReceivedStage = stage;
+                if (mWaitingStage == mReceivedStage) {
+                    notifyAll();
+                }
+            }
+        }
     }
 }
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
index ffe39e8..a83254b 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
@@ -59,27 +59,37 @@
 public final class ManualBenchmarkState {
     private static final String TAG = ManualBenchmarkState.class.getSimpleName();
 
-    @IntDef(prefix = {"STATS_REPORT"}, value = {
-            STATS_REPORT_MEDIAN,
-            STATS_REPORT_MEAN,
-            STATS_REPORT_MIN,
-            STATS_REPORT_MAX,
-            STATS_REPORT_PERCENTILE90,
-            STATS_REPORT_PERCENTILE95,
-            STATS_REPORT_STDDEV,
-            STATS_REPORT_ITERATION,
-    })
-    public @interface StatsReport {}
+    @Target(ElementType.ANNOTATION_TYPE)
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface StatsReport {
+        int FLAG_MEDIAN = 0x00000001;
+        int FLAG_MEAN = 0x00000002;
+        int FLAG_MIN = 0x00000004;
+        int FLAG_MAX = 0x00000008;
+        int FLAG_STDDEV = 0x00000010;
+        int FLAG_COEFFICIENT_VAR = 0x00000020;
+        int FLAG_ITERATION = 0x00000040;
 
-    public static final int STATS_REPORT_MEDIAN = 0x00000001;
-    public static final int STATS_REPORT_MEAN = 0x00000002;
-    public static final int STATS_REPORT_MIN = 0x00000004;
-    public static final int STATS_REPORT_MAX = 0x00000008;
-    public static final int STATS_REPORT_PERCENTILE90 = 0x00000010;
-    public static final int STATS_REPORT_PERCENTILE95 = 0x00000020;
-    public static final int STATS_REPORT_STDDEV = 0x00000040;
-    public static final int STATS_REPORT_COEFFICIENT_VAR = 0x00000080;
-    public static final int STATS_REPORT_ITERATION = 0x00000100;
+        @Retention(RetentionPolicy.RUNTIME)
+        @IntDef(value = {
+                FLAG_MEDIAN,
+                FLAG_MEAN,
+                FLAG_MIN,
+                FLAG_MAX,
+                FLAG_STDDEV,
+                FLAG_COEFFICIENT_VAR,
+                FLAG_ITERATION,
+        })
+        @interface Flag {}
+
+        /** Defines which type of statistics should output. */
+        @Flag int flags() default -1;
+        /** An array with value 0~100 to provide the percentiles. */
+        int[] percentiles() default {};
+    }
+
+    /** It means the entire {@link StatsReport} is not given. */
+    private static final int DEFAULT_STATS_REPORT = -2;
 
     // TODO: Tune these values.
     // warm-up for duration
@@ -116,8 +126,9 @@
     // The computation needs double precision, but long int is fine for final reporting.
     private Stats mStats;
 
-    private int mStatsReportFlags = STATS_REPORT_MEDIAN | STATS_REPORT_MEAN
-            | STATS_REPORT_PERCENTILE90 | STATS_REPORT_PERCENTILE95 | STATS_REPORT_STDDEV;
+    private int mStatsReportFlags =
+            StatsReport.FLAG_MEDIAN | StatsReport.FLAG_MEAN | StatsReport.FLAG_STDDEV;
+    private int[] mStatsReportPercentiles = {90 , 95};
 
     private boolean shouldReport(int statsReportFlag) {
         return (mStatsReportFlags & statsReportFlag) != 0;
@@ -136,9 +147,10 @@
         if (targetTestDurationNs >= 0) {
             mTargetTestDurationNs = targetTestDurationNs;
         }
-        final int statsReportFlags = testAnnotation.statsReportFlags();
-        if (statsReportFlags >= 0) {
-            mStatsReportFlags = statsReportFlags;
+        final StatsReport statsReport = testAnnotation.statsReport();
+        if (statsReport != null && statsReport.flags() != DEFAULT_STATS_REPORT) {
+            mStatsReportFlags = statsReport.flags();
+            mStatsReportPercentiles = statsReport.percentiles();
         }
     }
 
@@ -189,11 +201,20 @@
     }
 
     /**
-     * Adds additional result while this benchmark is running. It is used when a sequence of
+     * @return {@code true} if the benchmark is in warmup state. It can be used to skip the
+     *         operations or measurements that are unnecessary while the test isn't running the
+     *         actual benchmark.
+     */
+    public boolean isWarmingUp() {
+        return mState == WARMUP;
+    }
+
+    /**
+     * Adds additional result while this benchmark isn't warming up. It is used when a sequence of
      * operations is executed consecutively, the duration of each operation can also be recorded.
      */
     public void addExtraResult(String key, long duration) {
-        if (mState != RUNNING) {
+        if (isWarmingUp()) {
             return;
         }
         if (mExtraResults == null) {
@@ -221,31 +242,30 @@
     }
 
     private void fillStatus(Bundle status, String key, Stats stats) {
-        if (shouldReport(STATS_REPORT_ITERATION)) {
+        if (shouldReport(StatsReport.FLAG_ITERATION)) {
             status.putLong(key + "_iteration", stats.getSize());
         }
-        if (shouldReport(STATS_REPORT_MEDIAN)) {
+        if (shouldReport(StatsReport.FLAG_MEDIAN)) {
             status.putLong(key + "_median", stats.getMedian());
         }
-        if (shouldReport(STATS_REPORT_MEAN)) {
+        if (shouldReport(StatsReport.FLAG_MEAN)) {
             status.putLong(key + "_mean", Math.round(stats.getMean()));
         }
-        if (shouldReport(STATS_REPORT_MIN)) {
+        if (shouldReport(StatsReport.FLAG_MIN)) {
             status.putLong(key + "_min", stats.getMin());
         }
-        if (shouldReport(STATS_REPORT_MAX)) {
+        if (shouldReport(StatsReport.FLAG_MAX)) {
             status.putLong(key + "_max", stats.getMax());
         }
-        if (shouldReport(STATS_REPORT_PERCENTILE90)) {
-            status.putLong(key + "_percentile90", stats.getPercentile90());
+        if (mStatsReportPercentiles != null) {
+            for (int percentile : mStatsReportPercentiles) {
+                status.putLong(key + "_percentile" + percentile, stats.getPercentile(percentile));
+            }
         }
-        if (shouldReport(STATS_REPORT_PERCENTILE95)) {
-            status.putLong(key + "_percentile95", stats.getPercentile95());
-        }
-        if (shouldReport(STATS_REPORT_STDDEV)) {
+        if (shouldReport(StatsReport.FLAG_STDDEV)) {
             status.putLong(key + "_stddev", Math.round(stats.getStandardDeviation()));
         }
-        if (shouldReport(STATS_REPORT_COEFFICIENT_VAR)) {
+        if (shouldReport(StatsReport.FLAG_COEFFICIENT_VAR)) {
             status.putLong(key + "_cv",
                     Math.round((100 * stats.getStandardDeviation() / stats.getMean())));
         }
@@ -276,6 +296,6 @@
     public @interface ManualBenchmarkTest {
         long warmupDurationNs() default -1;
         long targetTestDurationNs() default -1;
-        @StatsReport int statsReportFlags() default -1;
+        StatsReport statsReport() default @StatsReport(flags = DEFAULT_STATS_REPORT);
     }
 }
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java b/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java
new file mode 100644
index 0000000..e934feb
--- /dev/null
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 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.perftests.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+/**
+ * A simple activity used for testing, e.g. performance of activity switching, or as a base
+ * container of testing view.
+ */
+public class PerfTestActivity extends Activity {
+    public static final String INTENT_EXTRA_KEEP_SCREEN_ON = "keep_screen_on";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (getIntent().getBooleanExtra(INTENT_EXTRA_KEEP_SCREEN_ON, false)) {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        }
+    }
+
+    public static Intent createLaunchIntent(Context context) {
+        final Intent intent = new Intent();
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setClass(context, PerfTestActivity.class);
+        return intent;
+    }
+}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
index f650e81..fb516a8 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
@@ -16,34 +16,34 @@
 
 package android.perftests.utils;
 
+import android.annotation.IntRange;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 public class Stats {
-    private long mMedian, mMin, mMax, mPercentile90, mPercentile95;
+    private long mMedian, mMin, mMax;
     private double mMean, mStandardDeviation;
-    private final int mSize;
+    private final List<Long> mValues;
 
     /* Calculate stats in constructor. */
     public Stats(List<Long> values) {
-        // make a copy since we're modifying it
-        values = new ArrayList<>(values);
         final int size = values.size();
         if (size < 2) {
             throw new IllegalArgumentException("At least two results are necessary.");
         }
 
+        // Make a copy since we're modifying it.
+        mValues = values = new ArrayList<>(values);
+
         Collections.sort(values);
 
-        mSize = size;
         mMin = values.get(0);
         mMax = values.get(values.size() - 1);
 
         mMedian = size % 2 == 0 ? (values.get(size / 2) + values.get(size / 2 - 1)) / 2 :
                 values.get(size / 2);
-        mPercentile90 = getPercentile(values, 90);
-        mPercentile95 = getPercentile(values, 95);
 
         for (int i = 0; i < size; ++i) {
             long result = values.get(i);
@@ -59,7 +59,7 @@
     }
 
     public int getSize() {
-        return mSize;
+        return mValues.size();
     }
 
     public double getMean() {
@@ -82,12 +82,8 @@
         return mStandardDeviation;
     }
 
-    public long getPercentile90() {
-        return mPercentile90;
-    }
-
-    public long getPercentile95() {
-        return mPercentile95;
+    public long getPercentile(@IntRange(from = 0, to = 100) int percentile) {
+        return getPercentile(mValues, percentile);
     }
 
     private static long getPercentile(List<Long> values, int percentile) {
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java b/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java
deleted file mode 100644
index 8f03f7e..0000000
--- a/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 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.perftests.utils;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-
-public class StubActivity extends Activity {
-    public static Intent createLaunchIntent(Context context) {
-        final Intent intent = new Intent();
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.setClass(context, StubActivity.class);
-        return intent;
-    }
-}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
new file mode 100644
index 0000000..1afed3a
--- /dev/null
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
@@ -0,0 +1,232 @@
+/*
+ * 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.perftests.utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Predicate;
+
+/**
+ * Utility to get the slice of tracing_mark_write S,F,B,E (Async: start, finish, Sync: begin, end).
+ * Use {@link #visit(String)} to process the trace in text form. The filtered results can be
+ * obtained by {@link #forAllSlices(BiConsumer)}.
+ *
+ * @see android.os.Trace
+ */
+public class TraceMarkParser {
+    /** All slices by the name of {@link TraceMarkLine}. */
+    private final Map<String, List<TraceMarkSlice>> mSlicesMap = new HashMap<>();
+    /** The nested depth of each task-pid. */
+    private final Map<String, Integer> mDepthMap = new HashMap<>();
+    /** The start trace lines that haven't matched the corresponding end. */
+    private final Map<String, TraceMarkLine> mPendingStarts = new HashMap<>();
+
+    private final Predicate<TraceMarkLine> mTraceLineFilter;
+
+    public TraceMarkParser(Predicate<TraceMarkLine> traceLineFilter) {
+        mTraceLineFilter = traceLineFilter;
+    }
+
+    /** Only accept the trace event with the given names. */
+    public TraceMarkParser(String... traceNames) {
+        this(line -> {
+            for (String name : traceNames) {
+                if (name.equals(line.name)) {
+                    return true;
+                }
+            }
+            return false;
+        });
+    }
+
+    /** Computes {@link TraceMarkSlice} by the given trace line. */
+    public void visit(String textTraceLine) {
+        final TraceMarkLine line = TraceMarkLine.parse(textTraceLine);
+        if (line == null) {
+            return;
+        }
+
+        if (line.isAsync) {
+            // Async-trace contains name in the start and finish event.
+            if (mTraceLineFilter.test(line)) {
+                if (line.isBegin) {
+                    mPendingStarts.put(line.name, line);
+                } else {
+                    final TraceMarkLine start = mPendingStarts.remove(line.name);
+                    if (start != null) {
+                        addSlice(start, line);
+                    }
+                }
+            }
+            return;
+        }
+
+        int depth = 1;
+        if (line.isBegin) {
+            final Integer existingDepth = mDepthMap.putIfAbsent(line.taskPid, 1);
+            if (existingDepth != null) {
+                mDepthMap.put(line.taskPid, depth = existingDepth + 1);
+            }
+            // Sync-trace only contains name in the begin event.
+            if (mTraceLineFilter.test(line)) {
+                mPendingStarts.put(getSyncPendingStartKey(line, depth), line);
+            }
+        } else {
+            final Integer existingDepth = mDepthMap.get(line.taskPid);
+            if (existingDepth != null) {
+                depth = existingDepth;
+                mDepthMap.put(line.taskPid, existingDepth - 1);
+            }
+            final TraceMarkLine begin = mPendingStarts.remove(getSyncPendingStartKey(line, depth));
+            if (begin != null) {
+                addSlice(begin, line);
+            }
+        }
+    }
+
+    private static String getSyncPendingStartKey(TraceMarkLine line, int depth) {
+        return line.taskPid + "@" + depth;
+    }
+
+    private void addSlice(TraceMarkLine begin, TraceMarkLine end) {
+        mSlicesMap.computeIfAbsent(
+                begin.name, k -> new ArrayList<>()).add(new TraceMarkSlice(begin, end));
+    }
+
+    public void forAllSlices(BiConsumer<String, List<TraceMarkSlice>> consumer) {
+        for (Map.Entry<String, List<TraceMarkSlice>> entry : mSlicesMap.entrySet()) {
+            consumer.accept(entry.getKey(), entry.getValue());
+        }
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        forAllSlices((key, slices) -> {
+            double totalMs = 0;
+            for (TraceMarkSlice s : slices) {
+                totalMs += s.getDurarionInSeconds() * 1000;
+            }
+            sb.append(key).append(" count=").append(slices.size()).append(" avg=")
+                    .append(totalMs / slices.size()).append("ms\n");
+        });
+
+        if (!mPendingStarts.isEmpty()) {
+            sb.append("[Warning] Unresolved events:").append(mPendingStarts).append("\n");
+        }
+        return sb.toString();
+    }
+
+    public static class TraceMarkSlice {
+        public final TraceMarkLine begin;
+        public final TraceMarkLine end;
+
+        TraceMarkSlice(TraceMarkLine begin, TraceMarkLine end) {
+            this.begin = begin;
+            this.end = end;
+        }
+
+        public double getDurarionInSeconds() {
+            return end.timestamp - begin.timestamp;
+        }
+    }
+
+    // taskPid                               timestamp                           name
+    // # Async:
+    // Binder:129_F-349  ( 1296) [003] ...1  12.2776: tracing_mark_write: S|1296|launching: a.test|0
+    // android.anim-135  ( 1296) [005] ...1  12.3361: tracing_mark_write: F|1296|launching: a.test|0
+    // # Normal:
+    // Binder:129_6-315  ( 1296) [007] ...1  97.4576: tracing_mark_write: B|1296|relayoutWindow: xxx
+    // ... there may have other nested begin/end
+    // Binder:129_6-315  ( 1296) [007] ...1  97.4580: tracing_mark_write: E|1296
+    public static class TraceMarkLine {
+        static final String EVENT_KEYWORD = ": tracing_mark_write: ";
+        static final char ASYNC_START = 'S';
+        static final char ASYNC_FINISH = 'F';
+        static final char SYNC_BEGIN = 'B';
+        static final char SYNC_END = 'E';
+
+        public final String taskPid;
+        public final double timestamp;
+        public final String name;
+        public final boolean isAsync;
+        public final boolean isBegin;
+
+        TraceMarkLine(String rawLine, int typePos, int type) throws IllegalArgumentException {
+            taskPid = rawLine.substring(0, rawLine.indexOf('(')).trim();
+            final int timeEnd = rawLine.indexOf(':', taskPid.length());
+            if (timeEnd < 0) {
+                throw new IllegalArgumentException("Timestamp end not found");
+            }
+            final int timeBegin = rawLine.lastIndexOf(' ', timeEnd);
+            if (timeBegin < 0) {
+                throw new IllegalArgumentException("Timestamp start not found");
+            }
+            timestamp = Double.parseDouble(rawLine.substring(timeBegin, timeEnd));
+            isAsync = type == ASYNC_START || type == ASYNC_FINISH;
+            isBegin = type == ASYNC_START || type == SYNC_BEGIN;
+
+            if (!isAsync && !isBegin) {
+                name = "";
+            } else {
+                // Get the position of the second '|' from "S|1234|name".
+                final int nameBegin = rawLine.indexOf('|', typePos + 2) + 1;
+                if (nameBegin == 0) {
+                    throw new IllegalArgumentException("Name begin not found");
+                }
+                if (isAsync) {
+                    // Get the name from "S|1234|name|0".
+                    name = rawLine.substring(nameBegin, rawLine.lastIndexOf('|'));
+                } else {
+                    name = rawLine.substring(nameBegin);
+                }
+            }
+        }
+
+        static TraceMarkLine parse(String rawLine) {
+            final int eventPos = rawLine.indexOf(EVENT_KEYWORD);
+            if (eventPos < 0) {
+                return null;
+            }
+            final int typePos = eventPos + EVENT_KEYWORD.length();
+            if (typePos >= rawLine.length()) {
+                return null;
+            }
+            final int type = rawLine.charAt(typePos);
+            if (type != ASYNC_START && type != ASYNC_FINISH
+                    && type != SYNC_BEGIN  && type != SYNC_END) {
+                return null;
+            }
+
+            try {
+                return new TraceMarkLine(rawLine, typePos, type);
+            } catch (IllegalArgumentException e) {
+                e.printStackTrace();
+            }
+            return null;
+        }
+
+        @Override
+        public String toString() {
+            return "TraceMarkLine{pid=" + taskPid + " time=" + timestamp + " name=" + name
+                    + " async=" + isAsync + " begin=" + isBegin + "}";
+        }
+    }
+}
diff --git a/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java b/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
index 9039f92..e27670c 100644
--- a/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
+++ b/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
@@ -17,10 +17,13 @@
 package android.os;
 
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.content.Context;
 
+import java.util.List;
+
 /**
  * Access to the service that keeps track of device idleness and drives low power mode based on
  * that.
@@ -66,4 +69,19 @@
             return new String[0];
         }
     }
+
+    /**
+     * Add the specified packages to the power save whitelist.
+     *
+     * @return the number of packages that were successfully added to the whitelist
+     */
+    @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+    public int addPowerSaveWhitelistApps(@NonNull List<String> packageNames) {
+        try {
+            return mService.addPowerSaveWhitelistApps(packageNames);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return 0;
+        }
+    }
 }
diff --git a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
index 9d5becb..20fb000 100644
--- a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
+++ b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
@@ -21,6 +21,7 @@
 /** @hide */
 interface IDeviceIdleController {
     void addPowerSaveWhitelistApp(String name);
+    int addPowerSaveWhitelistApps(in List<String> packageNames);
     void removePowerSaveWhitelistApp(String name);
     /* Removes an app from the system whitelist. Calling restoreSystemPowerWhitelistApp will add
     the app back into the system whitelist */
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 20ee064..4ee46f4 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -105,6 +105,8 @@
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.stream.Collectors;
 
 /**
@@ -1549,11 +1551,20 @@
             if (DEBUG) {
                 Slog.i(TAG, "addPowerSaveWhitelistApp(name = " + name + ")");
             }
+            addPowerSaveWhitelistApps(Collections.singletonList(name));
+        }
+
+        @Override
+        public int addPowerSaveWhitelistApps(List<String> packageNames) {
+            if (DEBUG) {
+                Slog.i(TAG,
+                        "addPowerSaveWhitelistApps(name = " + packageNames + ")");
+            }
             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
                     null);
             long ident = Binder.clearCallingIdentity();
             try {
-                addPowerSaveWhitelistAppInternal(name);
+                return addPowerSaveWhitelistAppsInternal(packageNames);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2188,21 +2199,35 @@
         }
     }
 
-    public boolean addPowerSaveWhitelistAppInternal(String name) {
+    private int addPowerSaveWhitelistAppsInternal(List<String> pkgNames) {
+        int numAdded = 0;
+        int numErrors = 0;
         synchronized (this) {
-            try {
-                ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(name,
-                        PackageManager.MATCH_ANY_USER);
-                if (mPowerSaveWhitelistUserApps.put(name, UserHandle.getAppId(ai.uid)) == null) {
-                    reportPowerSaveWhitelistChangedLocked();
-                    updateWhitelistAppIdsLocked();
-                    writeConfigFileLocked();
+            for (int i = pkgNames.size() - 1; i >= 0; --i) {
+                final String name = pkgNames.get(i);
+                if (name == null) {
+                    numErrors++;
+                    continue;
                 }
-                return true;
-            } catch (PackageManager.NameNotFoundException e) {
-                return false;
+                try {
+                    ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(name,
+                            PackageManager.MATCH_ANY_USER);
+                    if (mPowerSaveWhitelistUserApps.put(name, UserHandle.getAppId(ai.uid))
+                            == null) {
+                        numAdded++;
+                    }
+                } catch (PackageManager.NameNotFoundException e) {
+                    Slog.e(TAG, "Tried to add unknown package to power save whitelist: " + name);
+                    numErrors++;
+                }
+            }
+            if (numAdded > 0) {
+                reportPowerSaveWhitelistChangedLocked();
+                updateWhitelistAppIdsLocked();
+                writeConfigFileLocked();
             }
         }
+        return pkgNames.size() - numErrors;
     }
 
     public boolean removePowerSaveWhitelistAppInternal(String name) {
@@ -4070,7 +4095,8 @@
                         char op = arg.charAt(0);
                         String pkg = arg.substring(1);
                         if (op == '+') {
-                            if (addPowerSaveWhitelistAppInternal(pkg)) {
+                            if (addPowerSaveWhitelistAppsInternal(Collections.singletonList(pkg))
+                                    == 1) {
                                 pw.println("Added: " + pkg);
                             } else {
                                 pw.println("Unknown package: " + pkg);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index b97da59..aa7696d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -17,13 +17,8 @@
 package com.android.server.job.restrictions;
 
 import android.app.job.JobParameters;
-import android.content.Context;
-import android.os.IThermalService;
-import android.os.IThermalStatusListener;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.Temperature;
-import android.util.Slog;
+import android.os.PowerManager;
+import android.os.PowerManager.OnThermalStatusChangedListener;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.IndentingPrintWriter;
@@ -36,31 +31,29 @@
 
     private volatile boolean mIsThermalRestricted = false;
 
+    private PowerManager mPowerManager;
+
     public ThermalStatusRestriction(JobSchedulerService service) {
         super(service, JobParameters.REASON_DEVICE_THERMAL);
     }
 
     @Override
     public void onSystemServicesReady() {
-        final IThermalService thermalService = IThermalService.Stub.asInterface(
-                ServiceManager.getService(Context.THERMAL_SERVICE));
-        if (thermalService != null) {
-            try {
-                thermalService.registerThermalStatusListener(new IThermalStatusListener.Stub() {
-                    @Override
-                    public void onStatusChange(int status) {
-                        final boolean shouldBeActive = status >= Temperature.THROTTLING_SEVERE;
-                        if (mIsThermalRestricted == shouldBeActive) {
-                            return;
-                        }
-                        mIsThermalRestricted = shouldBeActive;
-                        mService.onControllerStateChanged();
-                    }
-                });
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to register thermal callback.", e);
+        mPowerManager = mService.getContext().getSystemService(PowerManager.class);
+        // Use MainExecutor
+        mPowerManager.addThermalStatusListener(new OnThermalStatusChangedListener() {
+            @Override
+            public void onThermalStatusChanged(int status) {
+                // This is called on the main thread. Do not do any slow operations in it.
+                // mService.onControllerStateChanged() will just post a message, which is okay.
+                final boolean shouldBeActive = status >= PowerManager.THERMAL_STATUS_SEVERE;
+                if (mIsThermalRestricted == shouldBeActive) {
+                    return;
+                }
+                mIsThermalRestricted = shouldBeActive;
+                mService.onControllerStateChanged();
             }
-        }
+        });
     }
 
     @Override
diff --git a/api/current.txt b/api/current.txt
index 677e8c2..b176675 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5872,6 +5872,7 @@
     method public android.service.notification.StatusBarNotification[] getActiveNotifications();
     method public android.app.AutomaticZenRule getAutomaticZenRule(String);
     method public java.util.Map<java.lang.String,android.app.AutomaticZenRule> getAutomaticZenRules();
+    method @Nullable public android.app.NotificationManager.Policy getConsolidatedNotificationPolicy();
     method public final int getCurrentInterruptionFilter();
     method public int getImportance();
     method public android.app.NotificationChannel getNotificationChannel(String);
@@ -9805,7 +9806,7 @@
     method public abstract java.io.File[] getExternalCacheDirs();
     method @Nullable public abstract java.io.File getExternalFilesDir(@Nullable String);
     method public abstract java.io.File[] getExternalFilesDirs(String);
-    method public abstract java.io.File[] getExternalMediaDirs();
+    method @Deprecated public abstract java.io.File[] getExternalMediaDirs();
     method public abstract java.io.File getFileStreamPath(String);
     method public abstract java.io.File getFilesDir();
     method public java.util.concurrent.Executor getMainExecutor();
@@ -12402,6 +12403,8 @@
 
   public class Resources {
     ctor @Deprecated public Resources(android.content.res.AssetManager, android.util.DisplayMetrics, android.content.res.Configuration);
+    method public void addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider, @IntRange(from=0) int);
+    method public int addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider);
     method public final void finishPreloading();
     method public final void flushLayoutCache();
     method @NonNull public android.content.res.XmlResourceParser getAnimation(@AnimatorRes @AnimRes int) throws android.content.res.Resources.NotFoundException;
@@ -12428,6 +12431,7 @@
     method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
     method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
     method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
+    method @NonNull public java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>> getLoaders();
     method @Deprecated public android.graphics.Movie getMovie(@RawRes int) throws android.content.res.Resources.NotFoundException;
     method @NonNull public String getQuantityString(@PluralsRes int, int, java.lang.Object...) throws android.content.res.Resources.NotFoundException;
     method @NonNull public String getQuantityString(@PluralsRes int, int) throws android.content.res.Resources.NotFoundException;
@@ -12455,6 +12459,8 @@
     method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException;
     method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException;
     method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public int removeLoader(@NonNull android.content.res.loader.ResourceLoader);
+    method public void setLoaders(@Nullable java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>>);
     method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
     field @AnyRes public static final int ID_NULL = 0; // 0x0
   }
@@ -12522,6 +12528,33 @@
 
 }
 
+package android.content.res.loader {
+
+  public class DirectoryResourceLoader implements android.content.res.loader.ResourceLoader {
+    ctor public DirectoryResourceLoader(@NonNull java.io.File);
+    method @Nullable public java.io.File findFile(@NonNull String);
+    method @NonNull public java.io.File getDirectory();
+  }
+
+  public interface ResourceLoader {
+    method @Nullable public default java.io.InputStream loadAsset(@NonNull String, int) throws java.io.IOException;
+    method @Nullable public default android.os.ParcelFileDescriptor loadAssetFd(@NonNull String) throws java.io.IOException;
+    method @Nullable public default android.graphics.drawable.Drawable loadDrawable(@NonNull android.util.TypedValue, int, int, @Nullable android.content.res.Resources.Theme);
+    method @Nullable public default android.content.res.XmlResourceParser loadXmlResourceParser(@NonNull String, @AnyRes int);
+  }
+
+  public final class ResourcesProvider implements java.lang.AutoCloseable java.io.Closeable {
+    method public void close();
+    method @NonNull public static android.content.res.loader.ResourcesProvider empty();
+    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
+    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.SharedMemory) throws java.io.IOException;
+    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
+    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.SharedMemory) throws java.io.IOException;
+    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
+  }
+
+}
+
 package android.database {
 
   public abstract class AbstractCursor implements android.database.CrossProcessCursor {
@@ -24619,6 +24652,7 @@
     field public static final int DolbyVisionLevelUhd30 = 64; // 0x40
     field public static final int DolbyVisionLevelUhd48 = 128; // 0x80
     field public static final int DolbyVisionLevelUhd60 = 256; // 0x100
+    field public static final int DolbyVisionProfileDvav110 = 1024; // 0x400
     field public static final int DolbyVisionProfileDvavPen = 2; // 0x2
     field public static final int DolbyVisionProfileDvavPer = 1; // 0x1
     field public static final int DolbyVisionProfileDvavSe = 512; // 0x200
@@ -25415,6 +25449,9 @@
     field public static final int METADATA_KEY_BITRATE = 20; // 0x14
     field public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25; // 0x19
     field public static final int METADATA_KEY_CD_TRACK_NUMBER = 0; // 0x0
+    field public static final int METADATA_KEY_COLOR_RANGE = 37; // 0x25
+    field public static final int METADATA_KEY_COLOR_STANDARD = 35; // 0x23
+    field public static final int METADATA_KEY_COLOR_TRANSFER = 36; // 0x24
     field public static final int METADATA_KEY_COMPILATION = 15; // 0xf
     field public static final int METADATA_KEY_COMPOSER = 4; // 0x4
     field public static final int METADATA_KEY_DATE = 5; // 0x5
@@ -38245,6 +38282,7 @@
     field public static final String COLUMN_MIME_TYPE = "mime_type";
     field public static final String COLUMN_SIZE = "_size";
     field public static final String COLUMN_SUMMARY = "summary";
+    field public static final int FLAG_DIR_BLOCKS_TREE = 32768; // 0x8000
     field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10
     field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20
     field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
@@ -38463,16 +38501,17 @@
 
   public static final class MediaStore.Audio {
     ctor public MediaStore.Audio();
-    method public static String keyFor(String);
+    method @Deprecated @Nullable public static String keyFor(@Nullable String);
   }
 
   public static interface MediaStore.Audio.AlbumColumns {
     field public static final String ALBUM = "album";
     field @Deprecated public static final String ALBUM_ART = "album_art";
     field public static final String ALBUM_ID = "album_id";
-    field public static final String ALBUM_KEY = "album_key";
+    field @Deprecated public static final String ALBUM_KEY = "album_key";
     field public static final String ARTIST = "artist";
     field public static final String ARTIST_ID = "artist_id";
+    field @Deprecated public static final String ARTIST_KEY = "artist_key";
     field public static final String FIRST_YEAR = "minyear";
     field public static final String LAST_YEAR = "maxyear";
     field public static final String NUMBER_OF_SONGS = "numsongs";
@@ -38491,7 +38530,7 @@
 
   public static interface MediaStore.Audio.ArtistColumns {
     field public static final String ARTIST = "artist";
-    field public static final String ARTIST_KEY = "artist_key";
+    field @Deprecated public static final String ARTIST_KEY = "artist_key";
     field public static final String NUMBER_OF_ALBUMS = "number_of_albums";
     field public static final String NUMBER_OF_TRACKS = "number_of_tracks";
   }
@@ -38514,19 +38553,23 @@
   public static interface MediaStore.Audio.AudioColumns extends android.provider.MediaStore.MediaColumns {
     field public static final String ALBUM = "album";
     field public static final String ALBUM_ID = "album_id";
-    field public static final String ALBUM_KEY = "album_key";
+    field @Deprecated public static final String ALBUM_KEY = "album_key";
     field public static final String ARTIST = "artist";
     field public static final String ARTIST_ID = "artist_id";
-    field public static final String ARTIST_KEY = "artist_key";
+    field @Deprecated public static final String ARTIST_KEY = "artist_key";
     field public static final String BOOKMARK = "bookmark";
     field public static final String COMPOSER = "composer";
+    field public static final String GENRE = "genre";
+    field public static final String GENRE_ID = "genre_id";
+    field @Deprecated public static final String GENRE_KEY = "genre_key";
     field public static final String IS_ALARM = "is_alarm";
     field public static final String IS_AUDIOBOOK = "is_audiobook";
     field public static final String IS_MUSIC = "is_music";
     field public static final String IS_NOTIFICATION = "is_notification";
     field public static final String IS_PODCAST = "is_podcast";
     field public static final String IS_RINGTONE = "is_ringtone";
-    field public static final String TITLE_KEY = "title_key";
+    field @Deprecated public static final String TITLE_KEY = "title_key";
+    field public static final String TITLE_RESOURCE_URI = "title_resource_uri";
     field public static final String TRACK = "track";
     field public static final String YEAR = "year";
   }
@@ -38752,6 +38795,9 @@
     field public static final String ARTIST = "artist";
     field public static final String BOOKMARK = "bookmark";
     field public static final String CATEGORY = "category";
+    field public static final String COLOR_RANGE = "color_range";
+    field public static final String COLOR_STANDARD = "color_standard";
+    field public static final String COLOR_TRANSFER = "color_transfer";
     field public static final String DESCRIPTION = "description";
     field public static final String IS_PRIVATE = "isprivate";
     field public static final String LANGUAGE = "language";
@@ -40810,7 +40856,7 @@
     field public static final String EXTRA_KEY_ALIAS = "android.security.extra.KEY_ALIAS";
     field public static final String EXTRA_NAME = "name";
     field public static final String EXTRA_PKCS12 = "PKCS12";
-    field public static final String KEY_ALIAS_SELECTION_DENIED = "alias-selection-denied-ef829e15-210a-409d-96c9-ee684fc41972";
+    field public static final String KEY_ALIAS_SELECTION_DENIED = "android:alias-selection-denied";
   }
 
   public interface KeyChainAliasCallback {
@@ -41871,6 +41917,7 @@
     field public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
     field public static final String ACTION_QS_TILE_PREFERENCES = "android.service.quicksettings.action.QS_TILE_PREFERENCES";
     field public static final String META_DATA_ACTIVE_TILE = "android.service.quicksettings.ACTIVE_TILE";
+    field public static final String META_DATA_BOOLEAN_TILE = "android.service.quicksettings.BOOLEAN_TILE";
   }
 
 }
@@ -44212,6 +44259,7 @@
     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";
+    field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array";
     field public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL = "display_hd_audio_property_bool";
     field public static final String KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL = "drop_video_call_when_answering_audio_call_bool";
     field public static final String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
@@ -44557,6 +44605,7 @@
     method public int describeContents();
     method public int getAsuLevel();
     method public int getDbm();
+    method public int getEcNo();
     method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR;
@@ -45062,6 +45111,7 @@
     method public long getDataLimitBytes();
     method public long getDataUsageBytes();
     method public long getDataUsageTime();
+    method @Nullable public int[] getNetworkTypes();
     method @Nullable public CharSequence getSummary();
     method @Nullable public CharSequence getTitle();
     method public void writeToParcel(android.os.Parcel, int);
@@ -45081,6 +45131,7 @@
     method public static android.telephony.SubscriptionPlan.Builder createRecurring(java.time.ZonedDateTime, java.time.Period);
     method public android.telephony.SubscriptionPlan.Builder setDataLimit(long, int);
     method public android.telephony.SubscriptionPlan.Builder setDataUsage(long, long);
+    method @NonNull public android.telephony.SubscriptionPlan.Builder setNetworkTypes(@Nullable int[]);
     method public android.telephony.SubscriptionPlan.Builder setSummary(@Nullable CharSequence);
     method public android.telephony.SubscriptionPlan.Builder setTitle(@Nullable CharSequence);
   }
@@ -45090,6 +45141,7 @@
     method @Nullable public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle);
     method public android.telephony.TelephonyManager createForSubscriptionId(int);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean doesSwitchMultiSimConfigTriggerReboot();
+    method public int getActiveModemCount();
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo();
     method public int getCallState();
     method public int getCardIdForDefaultEuicc();
@@ -45122,7 +45174,7 @@
     method public String getNetworkOperatorName();
     method public String getNetworkSpecifier();
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getNetworkType();
-    method public int getPhoneCount();
+    method @Deprecated public int getPhoneCount();
     method public int getPhoneType();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
     method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
@@ -45138,6 +45190,7 @@
     method public int getSimState();
     method public int getSimState(int);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getSubscriberId();
+    method public int getSupportedModemCount();
     method @Nullable public String getTypeAllocationCode();
     method @Nullable public String getTypeAllocationCode(int);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @NonNull public java.util.List<android.telephony.UiccCardInfo> getUiccCardsInfo();
@@ -45242,6 +45295,10 @@
     field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.extra.SUBSCRIPTION_ID";
     field public static final String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
     field public static final String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
+    field public static final int MODEM_COUNT_DUAL_MODEM = 2; // 0x2
+    field public static final int MODEM_COUNT_NO_MODEM = 0; // 0x0
+    field public static final int MODEM_COUNT_SINGLE_MODEM = 1; // 0x1
+    field public static final int MODEM_COUNT_TRI_MODEM = 3; // 0x3
     field public static final int MULTISIM_ALLOWED = 0; // 0x0
     field public static final int MULTISIM_NOT_SUPPORTED_BY_CARRIER = 2; // 0x2
     field public static final int MULTISIM_NOT_SUPPORTED_BY_HARDWARE = 1; // 0x1
diff --git a/api/lint-baseline.txt b/api/lint-baseline.txt
new file mode 100644
index 0000000..2ca8cf4
--- /dev/null
+++ b/api/lint-baseline.txt
@@ -0,0 +1,1179 @@
+// Baseline format: 1.0
+AcronymName: android.system.ErrnoException#rethrowAsIOException():
+    
+
+
+ActionValue: android.provider.Settings#ACTION_CONDITION_PROVIDER_SETTINGS:
+    
+
+
+AllUpper: android.media.MediaCodecInfo.CodecCapabilities#FEATURE_LowLatency:
+    
+
+
+ArrayReturn: android.app.Notification.MessagingStyle.Message#getMessagesFromBundleArray(android.os.Parcelable[]) parameter #0:
+    
+ArrayReturn: android.content.ContentProviderOperation#resolveExtrasBackReferences(android.content.ContentProviderResult[], int) parameter #0:
+    
+
+
+BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
+    
+BroadcastBehavior: android.app.admin.DevicePolicyManager#ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED:
+    
+BroadcastBehavior: android.app.admin.DevicePolicyManager#ACTION_MANAGED_PROFILE_PROVISIONED:
+    
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_DISCOVERY_FINISHED:
+    
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_DISCOVERY_STARTED:
+    
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_LOCAL_NAME_CHANGED:
+    
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_SCAN_MODE_CHANGED:
+    
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_STATE_CHANGED:
+    
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_ACL_CONNECTED:
+    
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECTED:
+    
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECT_REQUESTED:
+    
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_BOND_STATE_CHANGED:
+    
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_CLASS_CHANGED:
+    
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_FOUND:
+    
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_NAME_CHANGED:
+    
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_PAIRING_REQUEST:
+    
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_UUID:
+    
+BroadcastBehavior: android.content.Intent#ACTION_AIRPLANE_MODE_CHANGED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_APPLICATION_RESTRICTIONS_CHANGED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_BATTERY_CHANGED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_BATTERY_LOW:
+    
+BroadcastBehavior: android.content.Intent#ACTION_BATTERY_OKAY:
+    
+BroadcastBehavior: android.content.Intent#ACTION_CAMERA_BUTTON:
+    
+BroadcastBehavior: android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS:
+    
+BroadcastBehavior: android.content.Intent#ACTION_CONFIGURATION_CHANGED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_DATE_CHANGED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_DEVICE_STORAGE_LOW:
+    
+BroadcastBehavior: android.content.Intent#ACTION_DEVICE_STORAGE_OK:
+    
+BroadcastBehavior: android.content.Intent#ACTION_DOCK_EVENT:
+    
+BroadcastBehavior: android.content.Intent#ACTION_DREAMING_STARTED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_DREAMING_STOPPED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+    
+BroadcastBehavior: android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+    
+BroadcastBehavior: android.content.Intent#ACTION_GTALK_SERVICE_CONNECTED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_GTALK_SERVICE_DISCONNECTED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_HEADSET_PLUG:
+    
+BroadcastBehavior: android.content.Intent#ACTION_INPUT_METHOD_CHANGED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_LOCALE_CHANGED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MANAGE_PACKAGE_STORAGE:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_BAD_REMOVAL:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_BUTTON:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_CHECKING:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_EJECT:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_MOUNTED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_NOFS:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_REMOVED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_SCANNER_FINISHED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_SCANNER_SCAN_FILE:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_SCANNER_STARTED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_SHARED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_UNMOUNTABLE:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_UNMOUNTED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MY_PACKAGE_REPLACED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MY_PACKAGE_SUSPENDED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_MY_PACKAGE_UNSUSPENDED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_NEW_OUTGOING_CALL:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGES_SUSPENDED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGES_UNSUSPENDED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_ADDED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_CHANGED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_DATA_CLEARED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_FIRST_LAUNCH:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_FULLY_REMOVED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_INSTALL:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_NEEDS_VERIFICATION:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_REMOVED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_REPLACED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_RESTARTED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_VERIFIED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_POWER_CONNECTED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_POWER_DISCONNECTED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_PROVIDER_CHANGED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_REBOOT:
+    
+BroadcastBehavior: android.content.Intent#ACTION_SCREEN_OFF:
+    
+BroadcastBehavior: android.content.Intent#ACTION_SCREEN_ON:
+    
+BroadcastBehavior: android.content.Intent#ACTION_SHUTDOWN:
+    
+BroadcastBehavior: android.content.Intent#ACTION_TIMEZONE_CHANGED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_TIME_CHANGED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_TIME_TICK:
+    
+BroadcastBehavior: android.content.Intent#ACTION_UID_REMOVED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_UMS_CONNECTED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_UMS_DISCONNECTED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_USER_PRESENT:
+    
+BroadcastBehavior: android.content.Intent#ACTION_USER_UNLOCKED:
+    
+BroadcastBehavior: android.content.Intent#ACTION_WALLPAPER_CHANGED:
+    
+BroadcastBehavior: android.content.pm.PackageInstaller#ACTION_SESSION_COMMITTED:
+    
+BroadcastBehavior: android.content.pm.PackageInstaller#ACTION_SESSION_UPDATED:
+    
+BroadcastBehavior: android.hardware.Camera#ACTION_NEW_PICTURE:
+    
+BroadcastBehavior: android.hardware.Camera#ACTION_NEW_VIDEO:
+    
+BroadcastBehavior: android.hardware.input.InputManager#ACTION_QUERY_KEYBOARD_LAYOUTS:
+    
+BroadcastBehavior: android.hardware.usb.UsbManager#ACTION_USB_ACCESSORY_DETACHED:
+    
+BroadcastBehavior: android.hardware.usb.UsbManager#ACTION_USB_DEVICE_DETACHED:
+    
+BroadcastBehavior: android.media.AudioManager#ACTION_HDMI_AUDIO_PLUG:
+    
+BroadcastBehavior: android.media.AudioManager#ACTION_HEADSET_PLUG:
+    
+BroadcastBehavior: android.media.AudioManager#ACTION_MICROPHONE_MUTE_CHANGED:
+    
+BroadcastBehavior: android.media.AudioManager#ACTION_SPEAKERPHONE_STATE_CHANGED:
+    
+BroadcastBehavior: android.media.tv.TvContract#ACTION_INITIALIZE_PROGRAMS:
+    
+BroadcastBehavior: android.media.tv.TvContract#ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT:
+    
+BroadcastBehavior: android.media.tv.TvContract#ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED:
+    
+BroadcastBehavior: android.media.tv.TvContract#ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED:
+    
+BroadcastBehavior: android.net.ConnectivityManager#ACTION_BACKGROUND_DATA_SETTING_CHANGED:
+    
+BroadcastBehavior: android.net.Proxy#PROXY_CHANGE_ACTION:
+    
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
+    
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
+    
+BroadcastBehavior: android.os.DropBoxManager#ACTION_DROPBOX_ENTRY_ADDED:
+    
+BroadcastBehavior: android.provider.CalendarContract#ACTION_EVENT_REMINDER:
+    
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#DATA_SMS_RECEIVED_ACTION:
+    
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SECRET_CODE_ACTION:
+    
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SIM_FULL_ACTION:
+    
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_CB_RECEIVED_ACTION:
+    
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_DELIVER_ACTION:
+    
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION:
+    
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_REJECTED_ACTION:
+    
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION:
+    
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#WAP_PUSH_DELIVER_ACTION:
+    
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#WAP_PUSH_RECEIVED_ACTION:
+    
+BroadcastBehavior: android.security.KeyChain#ACTION_KEYCHAIN_CHANGED:
+    
+BroadcastBehavior: android.security.KeyChain#ACTION_KEY_ACCESS_CHANGED:
+    
+BroadcastBehavior: android.security.KeyChain#ACTION_STORAGE_CHANGED:
+    
+BroadcastBehavior: android.security.KeyChain#ACTION_TRUST_STORE_CHANGED:
+    
+BroadcastBehavior: android.speech.tts.TextToSpeech#ACTION_TTS_QUEUE_PROCESSING_COMPLETED:
+    
+BroadcastBehavior: android.speech.tts.TextToSpeech.Engine#ACTION_TTS_DATA_INSTALLED:
+    
+BroadcastBehavior: android.telephony.SubscriptionManager#ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED:
+    
+BroadcastBehavior: android.telephony.SubscriptionManager#ACTION_DEFAULT_SUBSCRIPTION_CHANGED:
+    
+BroadcastBehavior: android.telephony.SubscriptionManager#ACTION_REFRESH_SUBSCRIPTION_PLANS:
+    
+BroadcastBehavior: android.telephony.TelephonyManager#ACTION_SECRET_CODE:
+    
+BroadcastBehavior: android.telephony.TelephonyManager#ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED:
+    
+BroadcastBehavior: android.telephony.TelephonyManager#ACTION_SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED:
+    
+BroadcastBehavior: android.telephony.euicc.EuiccManager#ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE:
+    
+
+
+CompileTimeConstant: android.icu.util.JapaneseCalendar#REIWA:
+    
+
+
+DeprecationMismatch: android.accounts.AccountManager#newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle):
+    
+DeprecationMismatch: android.app.Activity#enterPictureInPictureMode():
+    
+DeprecationMismatch: android.app.Instrumentation#startAllocCounting():
+    
+DeprecationMismatch: android.app.Instrumentation#stopAllocCounting():
+    
+DeprecationMismatch: android.app.Notification#bigContentView:
+    
+DeprecationMismatch: android.app.Notification#contentView:
+    
+DeprecationMismatch: android.app.Notification#headsUpContentView:
+    
+DeprecationMismatch: android.app.Notification#tickerView:
+    
+DeprecationMismatch: android.app.Notification.Action.Builder#Builder(int, CharSequence, android.app.PendingIntent):
+    
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#getCancelLabel():
+    
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#getConfirmLabel():
+    
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#getInProgressLabel():
+    
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#setCancelLabel(CharSequence):
+    
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#setConfirmLabel(CharSequence):
+    
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#setInProgressLabel(CharSequence):
+    
+DeprecationMismatch: android.app.Notification.Builder#setContent(android.widget.RemoteViews):
+    
+DeprecationMismatch: android.app.Notification.Builder#setTicker(CharSequence, android.widget.RemoteViews):
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#getContentIcon():
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#getContentIconGravity():
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#getCustomContentHeight():
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#getCustomSizePreset():
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#getGravity():
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#getHintAvoidBackgroundClipping():
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#getHintHideIcon():
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#getHintScreenTimeout():
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#getHintShowBackgroundOnly():
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#setContentIcon(int):
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#setContentIconGravity(int):
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#setCustomContentHeight(int):
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#setCustomSizePreset(int):
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#setGravity(int):
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#setHintAvoidBackgroundClipping(boolean):
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#setHintHideIcon(boolean):
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#setHintScreenTimeout(int):
+    
+DeprecationMismatch: android.app.Notification.WearableExtender#setHintShowBackgroundOnly(boolean):
+    
+DeprecationMismatch: android.content.ContextWrapper#clearWallpaper():
+    
+DeprecationMismatch: android.content.ContextWrapper#getWallpaper():
+    
+DeprecationMismatch: android.content.ContextWrapper#getWallpaperDesiredMinimumHeight():
+    
+DeprecationMismatch: android.content.ContextWrapper#getWallpaperDesiredMinimumWidth():
+    
+DeprecationMismatch: android.content.ContextWrapper#peekWallpaper():
+    
+DeprecationMismatch: android.content.ContextWrapper#removeStickyBroadcast(android.content.Intent):
+    
+DeprecationMismatch: android.content.ContextWrapper#removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle):
+    
+DeprecationMismatch: android.content.ContextWrapper#sendStickyBroadcast(android.content.Intent):
+    
+DeprecationMismatch: android.content.ContextWrapper#sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle):
+    
+DeprecationMismatch: android.content.ContextWrapper#sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle):
+    
+DeprecationMismatch: android.content.ContextWrapper#sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle):
+    
+DeprecationMismatch: android.content.ContextWrapper#setWallpaper(android.graphics.Bitmap):
+    
+DeprecationMismatch: android.content.ContextWrapper#setWallpaper(java.io.InputStream):
+    
+DeprecationMismatch: android.database.CursorWrapper#deactivate():
+    
+DeprecationMismatch: android.database.CursorWrapper#requery():
+    
+DeprecationMismatch: android.graphics.ComposeShader#ComposeShader(android.graphics.Shader, android.graphics.Shader, android.graphics.Xfermode):
+    
+DeprecationMismatch: android.graphics.PixelFormat#A_8:
+    
+DeprecationMismatch: android.graphics.PixelFormat#LA_88:
+    
+DeprecationMismatch: android.graphics.PixelFormat#L_8:
+    
+DeprecationMismatch: android.graphics.PixelFormat#RGBA_4444:
+    
+DeprecationMismatch: android.graphics.PixelFormat#RGBA_5551:
+    
+DeprecationMismatch: android.graphics.PixelFormat#RGB_332:
+    
+DeprecationMismatch: android.net.wifi.WifiManager#EXTRA_BSSID:
+    
+DeprecationMismatch: android.net.wifi.WifiManager#EXTRA_WIFI_INFO:
+    
+DeprecationMismatch: android.opengl.EGL14#eglCreatePixmapSurface(android.opengl.EGLDisplay, android.opengl.EGLConfig, int, int[], int):
+    
+DeprecationMismatch: android.opengl.GLES20#GL_STENCIL_INDEX:
+    
+DeprecationMismatch: android.opengl.GLSurfaceView#surfaceRedrawNeeded(android.view.SurfaceHolder):
+    
+DeprecationMismatch: android.os.UserManager#setUserRestrictions(android.os.Bundle):
+    
+DeprecationMismatch: android.os.UserManager#setUserRestrictions(android.os.Bundle, android.os.UserHandle):
+    
+DeprecationMismatch: android.provider.Contacts.People#markAsContacted(android.content.ContentResolver, long):
+    
+DeprecationMismatch: android.renderscript.Type.CubemapFace#POSITVE_X:
+    
+DeprecationMismatch: android.renderscript.Type.CubemapFace#POSITVE_Y:
+    
+DeprecationMismatch: android.renderscript.Type.CubemapFace#POSITVE_Z:
+    
+DeprecationMismatch: android.speech.tts.TextToSpeech#areDefaultsEnforced():
+    
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_12HOUR:
+    
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_24HOUR:
+    
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_CAP_AMPM:
+    
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_CAP_MIDNIGHT:
+    
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_CAP_NOON:
+    
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_CAP_NOON_MIDNIGHT:
+    
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_NO_NOON_MIDNIGHT:
+    
+DeprecationMismatch: android.view.ViewGroup.LayoutParams#FILL_PARENT:
+    
+DeprecationMismatch: android.view.Window#setTitleColor(int):
+    
+DeprecationMismatch: android.view.accessibility.AccessibilityEvent#MAX_TEXT_LENGTH:
+    
+DeprecationMismatch: android.webkit.WebSettings#getSaveFormData():
+    
+DeprecationMismatch: android.webkit.WebView#shouldDelayChildPressedState():
+    
+DeprecationMismatch: android.webkit.WebViewDatabase#clearFormData():
+    
+DeprecationMismatch: android.webkit.WebViewDatabase#hasFormData():
+    
+DeprecationMismatch: javax.microedition.khronos.egl.EGL10#eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]):
+    
+
+
+GenericException: android.content.res.loader.ResourcesProvider#finalize():
+    Methods must not throw generic exceptions (`java.lang.Throwable`)
+
+
+HiddenSuperclass: android.content.res.ColorStateList:
+    
+HiddenSuperclass: android.graphics.Canvas:
+    
+HiddenSuperclass: android.graphics.RecordingCanvas:
+    
+HiddenSuperclass: android.hardware.biometrics.BiometricPrompt.AuthenticationCallback:
+    
+HiddenSuperclass: android.hardware.biometrics.BiometricPrompt.AuthenticationResult:
+    
+HiddenSuperclass: android.hardware.biometrics.BiometricPrompt.CryptoObject:
+    
+HiddenSuperclass: android.hardware.fingerprint.FingerprintManager.AuthenticationCallback:
+    
+HiddenSuperclass: android.hardware.fingerprint.FingerprintManager.CryptoObject:
+    
+HiddenSuperclass: android.media.AudioTrack:
+    
+HiddenSuperclass: android.media.MediaPlayer:
+    
+HiddenSuperclass: android.media.SoundPool:
+    
+HiddenSuperclass: android.service.autofill.CharSequenceTransformation:
+    
+HiddenSuperclass: android.service.autofill.DateTransformation:
+    
+HiddenSuperclass: android.service.autofill.DateValueSanitizer:
+    
+HiddenSuperclass: android.service.autofill.ImageTransformation:
+    
+HiddenSuperclass: android.service.autofill.LuhnChecksumValidator:
+    
+HiddenSuperclass: android.service.autofill.RegexValidator:
+    
+HiddenSuperclass: android.service.autofill.TextValueSanitizer:
+    
+HiddenSuperclass: android.service.autofill.VisibilitySetterAction:
+    
+HiddenSuperclass: android.util.StatsLog:
+    
+
+
+MissingNullability: android.app.AsyncNotedAppOp#equals(Object) parameter #0:
+    
+MissingNullability: android.app.AsyncNotedAppOp#writeToParcel(android.os.Parcel, int) parameter #0:
+    
+MissingNullability: android.app.SyncNotedAppOp#equals(Object) parameter #0:
+    
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#EGYPTIAN_HIEROGLYPH_FORMAT_CONTROLS:
+    
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#ELYMAIC:
+    
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#NANDINAGARI:
+    
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#NYIAKENG_PUACHUE_HMONG:
+    
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#OTTOMAN_SIYAQ_NUMBERS:
+    
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#SMALL_KANA_EXTENSION:
+    
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#SYMBOLS_AND_PICTOGRAPHS_EXTENDED_A:
+    
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#TAMIL_SUPPLEMENT:
+    
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#WANCHO:
+    
+MissingNullability: android.icu.text.DateTimePatternGenerator#getFieldDisplayName(int, android.icu.text.DateTimePatternGenerator.DisplayWidth):
+    
+MissingNullability: android.icu.text.DateTimePatternGenerator#getFieldDisplayName(int, android.icu.text.DateTimePatternGenerator.DisplayWidth) parameter #1:
+    
+MissingNullability: android.icu.util.VersionInfo#UNICODE_12_0:
+    
+MissingNullability: android.icu.util.VersionInfo#UNICODE_12_1:
+    
+MissingNullability: android.media.MediaMetadataRetriever#getFrameAtTime(long, int, android.media.MediaMetadataRetriever.BitmapParams):
+    
+MissingNullability: android.media.MediaMetadataRetriever#getScaledFrameAtTime(long, int, int, int, android.media.MediaMetadataRetriever.BitmapParams):
+    
+
+
+RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
+    
+RequiresPermission: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
+    
+RequiresPermission: android.app.AlarmManager#setTime(long):
+    
+RequiresPermission: android.app.AppOpsManager#isOpActive(String, int, String):
+    
+RequiresPermission: android.app.AppOpsManager#startWatchingActive(String[], java.util.concurrent.Executor, android.app.AppOpsManager.OnOpActiveChangedListener):
+    
+RequiresPermission: android.app.DownloadManager.Request#setDestinationInExternalPublicDir(String, String):
+    
+RequiresPermission: android.app.DownloadManager.Request#setDestinationUri(android.net.Uri):
+    
+RequiresPermission: android.app.DownloadManager.Request#setNotificationVisibility(int):
+    
+RequiresPermission: android.app.DownloadManager.Request#setShowRunningNotification(boolean):
+    
+RequiresPermission: android.app.Notification.Builder#setFullScreenIntent(android.app.PendingIntent, boolean):
+    
+RequiresPermission: android.app.Service#startForeground(int, android.app.Notification):
+    
+RequiresPermission: android.app.WallpaperInfo#getSettingsSliceUri():
+    
+RequiresPermission: android.app.WallpaperManager#clear():
+    
+RequiresPermission: android.app.WallpaperManager#clearWallpaper():
+    
+RequiresPermission: android.app.WallpaperManager#setBitmap(android.graphics.Bitmap):
+    
+RequiresPermission: android.app.WallpaperManager#setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean):
+    
+RequiresPermission: android.app.WallpaperManager#setDisplayPadding(android.graphics.Rect):
+    
+RequiresPermission: android.app.WallpaperManager#setResource(int):
+    
+RequiresPermission: android.app.WallpaperManager#setStream(java.io.InputStream):
+    
+RequiresPermission: android.app.WallpaperManager#setStream(java.io.InputStream, android.graphics.Rect, boolean):
+    
+RequiresPermission: android.app.WallpaperManager#suggestDesiredDimensions(int, int):
+    
+RequiresPermission: android.app.admin.DevicePolicyManager#bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle):
+    
+RequiresPermission: android.app.admin.DevicePolicyManager#getPasswordComplexity():
+    
+RequiresPermission: android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage(android.content.ComponentName, String, boolean):
+    
+RequiresPermission: android.app.backup.BackupManager#dataChanged(String):
+    
+RequiresPermission: android.app.usage.StorageStatsManager#queryExternalStatsForUser(java.util.UUID, android.os.UserHandle):
+    
+RequiresPermission: android.app.usage.StorageStatsManager#queryStatsForPackage(java.util.UUID, String, android.os.UserHandle):
+    
+RequiresPermission: android.app.usage.StorageStatsManager#queryStatsForUid(java.util.UUID, int):
+    
+RequiresPermission: android.app.usage.StorageStatsManager#queryStatsForUser(java.util.UUID, android.os.UserHandle):
+    
+RequiresPermission: android.app.usage.UsageStatsManager#queryAndAggregateUsageStats(long, long):
+    
+RequiresPermission: android.app.usage.UsageStatsManager#queryConfigurations(int, long, long):
+    
+RequiresPermission: android.app.usage.UsageStatsManager#queryEventStats(int, long, long):
+    
+RequiresPermission: android.app.usage.UsageStatsManager#queryEvents(long, long):
+    
+RequiresPermission: android.app.usage.UsageStatsManager#queryUsageStats(int, long, long):
+    
+RequiresPermission: android.appwidget.AppWidgetManager#bindAppWidgetIdIfAllowed(int, android.os.UserHandle, android.content.ComponentName, android.os.Bundle):
+    
+RequiresPermission: android.bluetooth.BluetoothA2dp#isA2dpPlaying(android.bluetooth.BluetoothDevice):
+    
+RequiresPermission: android.bluetooth.BluetoothAdapter#getName():
+    
+RequiresPermission: android.bluetooth.BluetoothDevice#setPin(byte[]):
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#abortReliableWrite():
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#beginReliableWrite():
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#disconnect():
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#discoverServices():
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#executeReliableWrite():
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#getService(java.util.UUID):
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#getServices():
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#readCharacteristic(android.bluetooth.BluetoothGattCharacteristic):
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#readDescriptor(android.bluetooth.BluetoothGattDescriptor):
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#readRemoteRssi():
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#requestMtu(int):
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean):
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic):
+    
+RequiresPermission: android.bluetooth.BluetoothGatt#writeDescriptor(android.bluetooth.BluetoothGattDescriptor):
+    
+RequiresPermission: android.bluetooth.BluetoothGattCharacteristic#BluetoothGattCharacteristic(java.util.UUID, int, int):
+    
+RequiresPermission: android.bluetooth.BluetoothGattCharacteristic#addDescriptor(android.bluetooth.BluetoothGattDescriptor):
+    
+RequiresPermission: android.bluetooth.BluetoothGattDescriptor#BluetoothGattDescriptor(java.util.UUID, int):
+    
+RequiresPermission: android.bluetooth.BluetoothGattServer#addService(android.bluetooth.BluetoothGattService):
+    
+RequiresPermission: android.bluetooth.BluetoothGattServer#cancelConnection(android.bluetooth.BluetoothDevice):
+    
+RequiresPermission: android.bluetooth.BluetoothGattServer#clearServices():
+    
+RequiresPermission: android.bluetooth.BluetoothGattServer#connect(android.bluetooth.BluetoothDevice, boolean):
+    
+RequiresPermission: android.bluetooth.BluetoothGattServer#getService(java.util.UUID):
+    
+RequiresPermission: android.bluetooth.BluetoothGattServer#getServices():
+    
+RequiresPermission: android.bluetooth.BluetoothGattServer#notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean):
+    
+RequiresPermission: android.bluetooth.BluetoothGattServer#removeService(android.bluetooth.BluetoothGattService):
+    
+RequiresPermission: android.bluetooth.BluetoothGattServer#sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]):
+    
+RequiresPermission: android.bluetooth.BluetoothGattService#BluetoothGattService(java.util.UUID, int):
+    
+RequiresPermission: android.bluetooth.BluetoothGattService#addCharacteristic(android.bluetooth.BluetoothGattCharacteristic):
+    
+RequiresPermission: android.bluetooth.BluetoothGattService#addService(android.bluetooth.BluetoothGattService):
+    
+RequiresPermission: android.bluetooth.BluetoothHeadset#isAudioConnected(android.bluetooth.BluetoothDevice):
+    
+RequiresPermission: android.bluetooth.BluetoothHeadset#sendVendorSpecificResultCode(android.bluetooth.BluetoothDevice, String, String):
+    
+RequiresPermission: android.bluetooth.BluetoothHeadset#startVoiceRecognition(android.bluetooth.BluetoothDevice):
+    
+RequiresPermission: android.bluetooth.BluetoothHeadset#stopVoiceRecognition(android.bluetooth.BluetoothDevice):
+    
+RequiresPermission: android.bluetooth.BluetoothHealth#connectChannelToSource(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration):
+    
+RequiresPermission: android.bluetooth.BluetoothHealth#disconnectChannel(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration, int):
+    
+RequiresPermission: android.bluetooth.BluetoothHealth#getConnectedDevices():
+    
+RequiresPermission: android.bluetooth.BluetoothHealth#getConnectionState(android.bluetooth.BluetoothDevice):
+    
+RequiresPermission: android.bluetooth.BluetoothHealth#getDevicesMatchingConnectionStates(int[]):
+    
+RequiresPermission: android.bluetooth.BluetoothHealth#getMainChannelFd(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration):
+    
+RequiresPermission: android.bluetooth.BluetoothHealth#registerSinkAppConfiguration(String, int, android.bluetooth.BluetoothHealthCallback):
+    
+RequiresPermission: android.bluetooth.BluetoothHealth#unregisterAppConfiguration(android.bluetooth.BluetoothHealthAppConfiguration):
+    
+RequiresPermission: android.bluetooth.le.AdvertisingSet#enableAdvertising(boolean, int, int):
+    
+RequiresPermission: android.bluetooth.le.BluetoothLeAdvertiser#startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback):
+    
+RequiresPermission: android.bluetooth.le.BluetoothLeAdvertiser#startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback):
+    
+RequiresPermission: android.bluetooth.le.BluetoothLeAdvertiser#stopAdvertising(android.bluetooth.le.AdvertiseCallback):
+    
+RequiresPermission: android.companion.CompanionDeviceManager#associate(android.companion.AssociationRequest, android.companion.CompanionDeviceManager.Callback, android.os.Handler):
+    
+RequiresPermission: android.content.ContentResolver#addPeriodicSync(android.accounts.Account, String, android.os.Bundle, long):
+    
+RequiresPermission: android.content.ContentResolver#cancelSync(android.content.SyncRequest):
+    
+RequiresPermission: android.content.ContentResolver#getCurrentSync():
+    
+RequiresPermission: android.content.ContentResolver#getCurrentSyncs():
+    
+RequiresPermission: android.content.ContentResolver#getIsSyncable(android.accounts.Account, String):
+    
+RequiresPermission: android.content.ContentResolver#getMasterSyncAutomatically():
+    
+RequiresPermission: android.content.ContentResolver#getPeriodicSyncs(android.accounts.Account, String):
+    
+RequiresPermission: android.content.ContentResolver#getSyncAutomatically(android.accounts.Account, String):
+    
+RequiresPermission: android.content.ContentResolver#isSyncActive(android.accounts.Account, String):
+    
+RequiresPermission: android.content.ContentResolver#isSyncPending(android.accounts.Account, String):
+    
+RequiresPermission: android.content.ContentResolver#removePeriodicSync(android.accounts.Account, String, android.os.Bundle):
+    
+RequiresPermission: android.content.ContentResolver#setIsSyncable(android.accounts.Account, String, int):
+    
+RequiresPermission: android.content.ContentResolver#setMasterSyncAutomatically(boolean):
+    
+RequiresPermission: android.content.ContentResolver#setSyncAutomatically(android.accounts.Account, String, boolean):
+    
+RequiresPermission: android.content.Context#clearWallpaper():
+    
+RequiresPermission: android.content.Context#getExternalCacheDir():
+    
+RequiresPermission: android.content.Context#getExternalCacheDirs():
+    
+RequiresPermission: android.content.Context#getExternalFilesDir(String):
+    
+RequiresPermission: android.content.Context#getExternalFilesDirs(String):
+    
+RequiresPermission: android.content.Context#getExternalMediaDirs():
+    
+RequiresPermission: android.content.Context#getObbDir():
+    
+RequiresPermission: android.content.Context#getObbDirs():
+    
+RequiresPermission: android.content.Context#removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle):
+    
+RequiresPermission: android.content.Context#setWallpaper(android.graphics.Bitmap):
+    
+RequiresPermission: android.content.Context#setWallpaper(java.io.InputStream):
+    
+RequiresPermission: android.content.pm.LauncherApps.Callback#onPackagesSuspended(String[], android.os.UserHandle, android.os.Bundle):
+    
+RequiresPermission: android.content.pm.PackageManager#canRequestPackageInstalls():
+    
+RequiresPermission: android.content.pm.PackageManager#getSuspendedPackageAppExtras():
+    
+RequiresPermission: android.content.pm.PackageManager#isPackageSuspended():
+    
+RequiresPermission: android.hardware.camera2.CameraCharacteristics#getKeysNeedingPermission():
+    
+RequiresPermission: android.hardware.usb.UsbManager#hasPermission(android.hardware.usb.UsbDevice):
+    
+RequiresPermission: android.hardware.usb.UsbManager#requestPermission(android.hardware.usb.UsbDevice, android.app.PendingIntent):
+    
+RequiresPermission: android.location.LocationManager#addGpsStatusListener(android.location.GpsStatus.Listener):
+    
+RequiresPermission: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener):
+    
+RequiresPermission: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler):
+    
+RequiresPermission: android.location.LocationManager#addNmeaListener(java.util.concurrent.Executor, android.location.OnNmeaMessageListener):
+    
+RequiresPermission: android.location.LocationManager#addProximityAlert(double, double, float, long, android.app.PendingIntent):
+    
+RequiresPermission: android.location.LocationManager#registerGnssStatusCallback(android.location.GnssStatus.Callback):
+    
+RequiresPermission: android.location.LocationManager#registerGnssStatusCallback(android.location.GnssStatus.Callback, android.os.Handler):
+    
+RequiresPermission: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
+    
+RequiresPermission: android.media.AudioManager#startBluetoothSco():
+    
+RequiresPermission: android.media.AudioManager#stopBluetoothSco():
+    
+RequiresPermission: android.media.MediaExtractor#setDataSource(String):
+    
+RequiresPermission: android.media.MediaExtractor#setDataSource(String, java.util.Map<java.lang.String,java.lang.String>):
+    
+RequiresPermission: android.media.MediaExtractor#setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String,java.lang.String>):
+    
+RequiresPermission: android.media.MediaPlayer#setWakeMode(android.content.Context, int):
+    
+RequiresPermission: android.media.MediaSession2Service#onUpdateNotification(android.media.MediaSession2):
+    
+RequiresPermission: android.media.RingtoneManager#getCursor():
+    
+RequiresPermission: android.media.RingtoneManager#getValidRingtoneUri(android.content.Context):
+    
+RequiresPermission: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName):
+    
+RequiresPermission: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler):
+    
+RequiresPermission: android.media.session.MediaSessionManager#getActiveSessions(android.content.ComponentName):
+    
+RequiresPermission: android.media.session.MediaSessionManager#isTrustedForMediaControl(android.media.session.MediaSessionManager.RemoteUserInfo):
+    
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.app.PendingIntent):
+    
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback):
+    
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler):
+    
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler, int):
+    
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, int):
+    
+RequiresPermission: android.net.sip.SipAudioCall#setSpeakerMode(boolean):
+    
+RequiresPermission: android.net.sip.SipAudioCall#startAudio():
+    
+RequiresPermission: android.net.wifi.WifiManager#getScanResults():
+    
+RequiresPermission: android.net.wifi.WifiManager#setWifiEnabled(boolean):
+    
+RequiresPermission: android.net.wifi.WifiManager#startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback, android.os.Handler):
+    
+RequiresPermission: android.net.wifi.WifiManager#startScan():
+    
+RequiresPermission: android.net.wifi.aware.IdentityChangedListener#onIdentityChanged(byte[]):
+    
+RequiresPermission: android.net.wifi.aware.WifiAwareManager#attach(android.net.wifi.aware.AttachCallback, android.net.wifi.aware.IdentityChangedListener, android.os.Handler):
+    
+RequiresPermission: android.net.wifi.aware.WifiAwareSession#publish(android.net.wifi.aware.PublishConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler):
+    
+RequiresPermission: android.net.wifi.aware.WifiAwareSession#subscribe(android.net.wifi.aware.SubscribeConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler):
+    
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
+    
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundNdefPush(android.app.Activity):
+    
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
+    
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage):
+    
+RequiresPermission: android.nfc.NfcAdapter#setBeamPushUris(android.net.Uri[], android.app.Activity):
+    
+RequiresPermission: android.nfc.NfcAdapter#setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity):
+    
+RequiresPermission: android.nfc.NfcAdapter#setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...):
+    
+RequiresPermission: android.nfc.NfcAdapter#setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...):
+    
+RequiresPermission: android.nfc.NfcAdapter#setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...):
+    
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
+    
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
+    
+RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
+    
+RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
+    
+RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
+    
+RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
+    
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
+    
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
+    
+RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
+    
+RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
+    
+RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
+    
+RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
+    
+RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
+    
+RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
+    
+RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
+    
+RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
+    
+RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
+    
+RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
+    
+RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
+    
+RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
+    
+RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
+    
+RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
+    
+RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
+    
+RequiresPermission: android.nfc.tech.Ndef#isWritable():
+    
+RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
+    
+RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
+    
+RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
+    
+RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
+    
+RequiresPermission: android.nfc.tech.NfcA#getTimeout():
+    
+RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
+    
+RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
+    
+RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
+    
+RequiresPermission: android.nfc.tech.NfcF#getTimeout():
+    
+RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
+    
+RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
+    
+RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
+    
+RequiresPermission: android.nfc.tech.TagTechnology#close():
+    
+RequiresPermission: android.nfc.tech.TagTechnology#connect():
+    
+RequiresPermission: android.os.Build#getSerial():
+    
+RequiresPermission: android.os.Debug#dumpService(String, java.io.FileDescriptor, String[]):
+    
+RequiresPermission: android.os.Environment#getExternalStorageDirectory():
+    
+RequiresPermission: android.os.PowerManager#newWakeLock(int, String):
+    
+RequiresPermission: android.os.PowerManager#reboot(String):
+    
+RequiresPermission: android.os.RecoverySystem#rebootWipeUserData(android.content.Context):
+    
+RequiresPermission: android.os.StrictMode.VmPolicy.Builder#detectFileUriExposure():
+    
+RequiresPermission: android.os.UserManager#getUserName():
+    
+RequiresPermission: android.os.UserManager#isUserUnlocked(android.os.UserHandle):
+    
+RequiresPermission: android.os.health.SystemHealthManager#takeUidSnapshot(int):
+    
+RequiresPermission: android.os.health.SystemHealthManager#takeUidSnapshots(int[]):
+    
+RequiresPermission: android.os.storage.StorageVolume#createAccessIntent(String):
+    
+RequiresPermission: android.provider.MediaStore#setRequireOriginal(android.net.Uri):
+    
+RequiresPermission: android.provider.Settings#canDrawOverlays(android.content.Context):
+    
+RequiresPermission: android.provider.Settings.System#canWrite(android.content.Context):
+    
+RequiresPermission: android.telecom.TelecomManager#acceptHandover(android.net.Uri, int, android.telecom.PhoneAccountHandle):
+    
+RequiresPermission: android.telecom.TelecomManager#acceptRingingCall():
+    
+RequiresPermission: android.telecom.TelecomManager#acceptRingingCall(int):
+    
+RequiresPermission: android.telecom.TelecomManager#addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle):
+    
+RequiresPermission: android.telecom.TelecomManager#cancelMissedCallsNotification():
+    
+RequiresPermission: android.telecom.TelecomManager#endCall():
+    
+RequiresPermission: android.telecom.TelecomManager#getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle):
+    
+RequiresPermission: android.telecom.TelecomManager#getCallCapablePhoneAccounts():
+    
+RequiresPermission: android.telecom.TelecomManager#getDefaultOutgoingPhoneAccount(String):
+    
+RequiresPermission: android.telecom.TelecomManager#getLine1Number(android.telecom.PhoneAccountHandle):
+    
+RequiresPermission: android.telecom.TelecomManager#getSelfManagedPhoneAccounts():
+    
+RequiresPermission: android.telecom.TelecomManager#getVoiceMailNumber(android.telecom.PhoneAccountHandle):
+    
+RequiresPermission: android.telecom.TelecomManager#handleMmi(String):
+    
+RequiresPermission: android.telecom.TelecomManager#handleMmi(String, android.telecom.PhoneAccountHandle):
+    
+RequiresPermission: android.telecom.TelecomManager#isInCall():
+    
+RequiresPermission: android.telecom.TelecomManager#isInManagedCall():
+    
+RequiresPermission: android.telecom.TelecomManager#isVoiceMailNumber(android.telecom.PhoneAccountHandle, String):
+    
+RequiresPermission: android.telecom.TelecomManager#placeCall(android.net.Uri, android.os.Bundle):
+    
+RequiresPermission: android.telecom.TelecomManager#showInCallScreen(boolean):
+    
+RequiresPermission: android.telecom.TelecomManager#silenceRinger():
+    
+RequiresPermission: android.telephony.CarrierConfigManager#getConfig():
+    
+RequiresPermission: android.telephony.CarrierConfigManager#getConfigByComponentForSubId(String, int):
+    
+RequiresPermission: android.telephony.CarrierConfigManager#getConfigForSubId(int):
+    
+RequiresPermission: android.telephony.PhoneStateListener#onCallStateChanged(int, String):
+    
+RequiresPermission: android.telephony.SmsManager#injectSmsPdu(byte[], String, android.app.PendingIntent):
+    
+RequiresPermission: android.telephony.SmsManager#sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent):
+    
+RequiresPermission: android.telephony.SmsManager#sendMultipartTextMessage(String, String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>):
+    
+RequiresPermission: android.telephony.SmsManager#sendTextMessage(String, String, String, android.app.PendingIntent, android.app.PendingIntent):
+    
+RequiresPermission: android.telephony.SmsManager#sendTextMessageWithoutPersisting(String, String, String, android.app.PendingIntent, android.app.PendingIntent):
+    
+RequiresPermission: android.telephony.SubscriptionManager#addSubscriptionsIntoGroup(java.util.List<java.lang.Integer>, android.os.ParcelUuid):
+    
+RequiresPermission: android.telephony.SubscriptionManager#createSubscriptionGroup(java.util.List<java.lang.Integer>):
+    
+RequiresPermission: android.telephony.SubscriptionManager#getActiveSubscriptionInfo(int):
+    
+RequiresPermission: android.telephony.SubscriptionManager#getActiveSubscriptionInfoCount():
+    
+RequiresPermission: android.telephony.SubscriptionManager#getActiveSubscriptionInfoForSimSlotIndex(int):
+    
+RequiresPermission: android.telephony.SubscriptionManager#getActiveSubscriptionInfoList():
+    
+RequiresPermission: android.telephony.SubscriptionManager#getOpportunisticSubscriptions():
+    
+RequiresPermission: android.telephony.SubscriptionManager#getSubscriptionsInGroup(android.os.ParcelUuid):
+    
+RequiresPermission: android.telephony.SubscriptionManager#removeSubscriptionsFromGroup(java.util.List<java.lang.Integer>, android.os.ParcelUuid):
+    
+RequiresPermission: android.telephony.SubscriptionManager#setOpportunistic(boolean, int):
+    
+RequiresPermission: android.telephony.TelephonyManager#doesSwitchMultiSimConfigTriggerReboot():
+    
+RequiresPermission: android.telephony.TelephonyManager#getCarrierConfig():
+    
+RequiresPermission: android.telephony.TelephonyManager#getDataNetworkType():
+    
+RequiresPermission: android.telephony.TelephonyManager#getDeviceId():
+    
+RequiresPermission: android.telephony.TelephonyManager#getDeviceId(int):
+    
+RequiresPermission: android.telephony.TelephonyManager#getDeviceSoftwareVersion():
+    
+RequiresPermission: android.telephony.TelephonyManager#getEmergencyNumberList():
+    
+RequiresPermission: android.telephony.TelephonyManager#getEmergencyNumberList(int):
+    
+RequiresPermission: android.telephony.TelephonyManager#getForbiddenPlmns():
+    
+RequiresPermission: android.telephony.TelephonyManager#getGroupIdLevel1():
+    
+RequiresPermission: android.telephony.TelephonyManager#getImei(int):
+    
+RequiresPermission: android.telephony.TelephonyManager#getLine1Number():
+    
+RequiresPermission: android.telephony.TelephonyManager#getMeid():
+    
+RequiresPermission: android.telephony.TelephonyManager#getMeid(int):
+    
+RequiresPermission: android.telephony.TelephonyManager#getNai():
+    
+RequiresPermission: android.telephony.TelephonyManager#getPreferredOpportunisticDataSubscription():
+    
+RequiresPermission: android.telephony.TelephonyManager#getServiceState():
+    
+RequiresPermission: android.telephony.TelephonyManager#getSimSerialNumber():
+    
+RequiresPermission: android.telephony.TelephonyManager#getSubscriberId():
+    
+RequiresPermission: android.telephony.TelephonyManager#getVisualVoicemailPackageName():
+    
+RequiresPermission: android.telephony.TelephonyManager#getVoiceMailAlphaTag():
+    
+RequiresPermission: android.telephony.TelephonyManager#getVoiceMailNumber():
+    
+RequiresPermission: android.telephony.TelephonyManager#getVoiceNetworkType():
+    
+RequiresPermission: android.telephony.TelephonyManager#iccCloseLogicalChannel(int):
+    
+RequiresPermission: android.telephony.TelephonyManager#iccExchangeSimIO(int, int, int, int, int, String):
+    
+RequiresPermission: android.telephony.TelephonyManager#iccOpenLogicalChannel(String):
+    
+RequiresPermission: android.telephony.TelephonyManager#iccOpenLogicalChannel(String, int):
+    
+RequiresPermission: android.telephony.TelephonyManager#iccTransmitApduBasicChannel(int, int, int, int, int, String):
+    
+RequiresPermission: android.telephony.TelephonyManager#iccTransmitApduLogicalChannel(int, int, int, int, int, int, String):
+    
+RequiresPermission: android.telephony.TelephonyManager#isDataEnabled():
+    
+RequiresPermission: android.telephony.TelephonyManager#isDataRoamingEnabled():
+    
+RequiresPermission: android.telephony.TelephonyManager#isMultiSimSupported():
+    
+RequiresPermission: android.telephony.TelephonyManager#requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback):
+    
+RequiresPermission: android.telephony.TelephonyManager#sendEnvelopeWithStatus(String):
+    
+RequiresPermission: android.telephony.TelephonyManager#sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler):
+    
+RequiresPermission: android.telephony.TelephonyManager#sendVisualVoicemailSms(String, int, String, android.app.PendingIntent):
+    
+RequiresPermission: android.telephony.TelephonyManager#setDataEnabled(boolean):
+    
+RequiresPermission: android.telephony.TelephonyManager#setNetworkSelectionModeAutomatic():
+    
+RequiresPermission: android.telephony.TelephonyManager#setNetworkSelectionModeManual(String, boolean):
+    
+RequiresPermission: android.telephony.TelephonyManager#setPreferredOpportunisticDataSubscription(int, boolean, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
+    
+RequiresPermission: android.telephony.TelephonyManager#setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri):
+    
+RequiresPermission: android.telephony.TelephonyManager#setVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle, boolean):
+    
+RequiresPermission: android.telephony.TelephonyManager#switchMultiSimConfig(int):
+    
+RequiresPermission: android.telephony.TelephonyManager#updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
+    
+RequiresPermission: android.telephony.euicc.EuiccManager#deleteSubscription(int, android.app.PendingIntent):
+    
+RequiresPermission: android.telephony.euicc.EuiccManager#downloadSubscription(android.telephony.euicc.DownloadableSubscription, boolean, android.app.PendingIntent):
+    
+RequiresPermission: android.telephony.euicc.EuiccManager#switchToSubscription(int, android.app.PendingIntent):
+    
+RequiresPermission: android.telephony.euicc.EuiccManager#updateSubscriptionNickname(int, String, android.app.PendingIntent):
+    
+RequiresPermission: android.view.inputmethod.InputMethodManager#setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype):
+    
+RequiresPermission: android.view.inputmethod.InputMethodManager#setInputMethod(android.os.IBinder, String):
+    
+RequiresPermission: android.view.inputmethod.InputMethodManager#setInputMethodAndSubtype(android.os.IBinder, String, android.view.inputmethod.InputMethodSubtype):
+    
+RequiresPermission: android.webkit.WebSettings#setBlockNetworkLoads(boolean):
+    
+RequiresPermission: android.webkit.WebSettings#setGeolocationEnabled(boolean):
+    
+
+
+SamShouldBeLast: android.location.LocationManager#registerGnssMeasurementsCallback(java.util.concurrent.Executor, android.location.GnssMeasurementsEvent.Callback):
+    
+SamShouldBeLast: android.location.LocationManager#registerGnssNavigationMessageCallback(java.util.concurrent.Executor, android.location.GnssNavigationMessage.Callback):
+    
+SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
+    
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener):
+    
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener):
+    
+
+
+StreamFiles: android.content.res.loader.DirectoryResourceLoader#DirectoryResourceLoader(java.io.File):
+    Methods accepting `File` should also accept `FileDescriptor` or streams: constructor android.content.res.loader.DirectoryResourceLoader(java.io.File)
+
+
+Todo: android.hardware.camera2.params.StreamConfigurationMap:
+    
+Todo: android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration(Class<T>, android.util.Size):
+    
+Todo: android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration(int, android.util.Size):
+    
+Todo: android.provider.ContactsContract.RawContacts#newEntityIterator(android.database.Cursor):
+    
+Todo: android.telephony.CarrierConfigManager#KEY_USE_OTASP_FOR_PROVISIONING_BOOL:
+    
diff --git a/api/system-current.txt b/api/system-current.txt
index 447ba30..6e35200 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -172,6 +172,7 @@
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
     field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
+    field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS";
     field public static final String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
     field public static final String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT";
     field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
@@ -1359,7 +1360,7 @@
 
   public abstract class Context {
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean bindServiceAsUser(@RequiresPermission android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
-    method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle);
+    method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
     method public abstract android.content.Context createCredentialProtectedStorageContext();
     method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract java.io.File getPreloadsFileCache();
@@ -1754,7 +1755,9 @@
     field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
     field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
     field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
+    field public static final int PROTECTION_FLAG_TELEPHONY = 4194304; // 0x400000
     field public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000
+    field public static final int PROTECTION_FLAG_WIFI = 8388608; // 0x800000
     field @Nullable public final String backgroundPermission;
     field @StringRes public int requestRes;
   }
@@ -3420,7 +3423,9 @@
   public class Location implements android.os.Parcelable {
     method public boolean isComplete();
     method public void makeComplete();
+    method public void setExtraLocation(@Nullable String, @Nullable android.location.Location);
     method public void setIsFromMockProvider(boolean);
+    field public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
   }
 
   public class LocationManager {
@@ -5697,7 +5702,10 @@
 package android.permission {
 
   public final class PermissionControllerManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
     method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
     field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
     field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
     field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
@@ -5711,17 +5719,19 @@
 
   public abstract class PermissionControllerService extends android.app.Service {
     ctor public PermissionControllerService();
+    method @BinderThread public void onApplyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @NonNull public final android.os.IBinder onBind(android.content.Intent);
     method @BinderThread public abstract void onCountPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull java.util.function.IntConsumer);
     method @BinderThread public abstract void onGetAppPermissions(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionPresentationInfo>>);
     method @BinderThread public abstract void onGetPermissionUsages(boolean, long, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionUsageInfo>>);
     method @BinderThread public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream, @NonNull Runnable);
     method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable);
-    method @BinderThread public abstract void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
-    method @BinderThread public abstract void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
+    method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
     method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
     method @BinderThread public void onUpdateUserSensitive();
     field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
   }
@@ -7312,7 +7322,7 @@
   public abstract class CellBroadcastService extends android.app.Service {
     ctor public CellBroadcastService();
     method @CallSuper public android.os.IBinder onBind(android.content.Intent);
-    method public abstract void onCdmaCellBroadcastSms(int, byte[]);
+    method public abstract void onCdmaCellBroadcastSms(int, byte[], int);
     method public abstract void onGsmCellBroadcastSms(int, byte[]);
     field public static final String CELL_BROADCAST_SERVICE_INTERFACE = "android.telephony.CellBroadcastService";
   }
@@ -9576,17 +9586,17 @@
 
   public class ImsSmsImplBase {
     ctor public ImsSmsImplBase();
-    method public void acknowledgeSms(int, int, int);
-    method public void acknowledgeSmsReport(int, int, int);
+    method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int);
+    method public void acknowledgeSmsReport(int, @IntRange(from=0, to=65535) int, int);
     method public String getSmsFormat();
     method public void onReady();
-    method @Deprecated public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException;
-    method public final void onSendSmsResultError(int, int, int, int, int) throws java.lang.RuntimeException;
-    method public final void onSendSmsResultSuccess(int, int) throws java.lang.RuntimeException;
+    method @Deprecated public final void onSendSmsResult(int, @IntRange(from=0, to=65535) int, int, int) throws java.lang.RuntimeException;
+    method public final void onSendSmsResultError(int, @IntRange(from=0, to=65535) int, int, int, int) throws java.lang.RuntimeException;
+    method public final void onSendSmsResultSuccess(int, @IntRange(from=0, to=65535) int) throws java.lang.RuntimeException;
     method public final void onSmsReceived(int, String, byte[]) throws java.lang.RuntimeException;
-    method @Deprecated public final void onSmsStatusReportReceived(int, int, String, byte[]) throws java.lang.RuntimeException;
+    method @Deprecated public final void onSmsStatusReportReceived(int, @IntRange(from=0, to=65535) int, String, byte[]) throws java.lang.RuntimeException;
     method public final void onSmsStatusReportReceived(int, String, byte[]) throws java.lang.RuntimeException;
-    method public void sendSms(int, int, String, String, boolean, byte[]);
+    method public void sendSms(int, @IntRange(from=0, to=65535) int, String, String, boolean, byte[]);
     field public static final int DELIVER_STATUS_ERROR_GENERIC = 2; // 0x2
     field public static final int DELIVER_STATUS_ERROR_NO_MEMORY = 3; // 0x3
     field public static final int DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED = 4; // 0x4
@@ -9758,9 +9768,10 @@
     method public final long getUserActivityTimeout();
     method public final void setUserActivityTimeout(long);
     field @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 524288; // 0x80000
+    field @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW) public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 16; // 0x10
   }
 
-  @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags {
+  @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags {
   }
 
 }
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
new file mode 100644
index 0000000..57a853a
--- /dev/null
+++ b/api/system-lint-baseline.txt
@@ -0,0 +1,391 @@
+// Baseline format: 1.0
+ActionValue: android.location.Location#EXTRA_NO_GPS_LOCATION:
+    
+
+
+ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions():
+    
+
+
+GenericException: android.app.prediction.AppPredictor#finalize():
+    
+GenericException: android.hardware.location.ContextHubClient#finalize():
+    
+GenericException: android.net.IpSecManager.IpSecTunnelInterface#finalize():
+    
+GenericException: android.service.autofill.augmented.FillWindow#finalize():
+    
+
+
+InterfaceConstant: android.service.storage.ExternalStorageService#SERVICE_INTERFACE:
+    
+
+
+KotlinKeyword: android.app.Notification#when:
+    
+
+
+MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#toString():
+    
+MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#writeToParcel(android.os.Parcel, int) parameter #0:
+    
+MissingNullability: android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged(android.content.ComponentName) parameter #0:
+    
+MissingNullability: android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged(android.media.session.MediaSession.Token) parameter #0:
+    
+MissingNullability: android.media.session.MediaSessionManager.Callback#onMediaKeyEventDispatched(android.view.KeyEvent, android.content.ComponentName) parameter #0:
+    
+MissingNullability: android.media.session.MediaSessionManager.Callback#onMediaKeyEventDispatched(android.view.KeyEvent, android.content.ComponentName) parameter #1:
+    
+MissingNullability: android.media.session.MediaSessionManager.Callback#onMediaKeyEventDispatched(android.view.KeyEvent, android.media.session.MediaSession.Token) parameter #0:
+    
+MissingNullability: android.media.session.MediaSessionManager.Callback#onMediaKeyEventDispatched(android.view.KeyEvent, android.media.session.MediaSession.Token) parameter #1:
+    
+MissingNullability: android.media.soundtrigger.SoundTriggerDetectionService#onUnbind(android.content.Intent) parameter #0:
+    
+MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #0:
+    
+MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #1:
+    
+MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #2:
+    
+MissingNullability: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig):
+    
+MissingNullability: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context) parameter #0:
+    
+MissingNullability: android.provider.ContactsContract.MetadataSync#CONTENT_URI:
+    
+MissingNullability: android.provider.ContactsContract.MetadataSync#METADATA_AUTHORITY_URI:
+    
+MissingNullability: android.provider.ContactsContract.MetadataSyncState#CONTENT_URI:
+    
+MissingNullability: android.provider.SearchIndexablesProvider#attachInfo(android.content.Context, android.content.pm.ProviderInfo) parameter #0:
+    
+MissingNullability: android.provider.SearchIndexablesProvider#attachInfo(android.content.Context, android.content.pm.ProviderInfo) parameter #1:
+    
+MissingNullability: android.service.autofill.augmented.AugmentedAutofillService#onUnbind(android.content.Intent) parameter #0:
+    
+MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
+    
+MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #1:
+    
+MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #2:
+    
+MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0:
+    
+MissingNullability: android.telecom.CallScreeningService.CallResponse.Builder#setShouldScreenCallFurther(boolean):
+    
+MissingNullability: android.telephony.CallerInfo#toString():
+    
+MissingNullability: android.telephony.CellBroadcastService#onBind(android.content.Intent):
+    
+MissingNullability: android.telephony.CellBroadcastService#onBind(android.content.Intent) parameter #0:
+    
+MissingNullability: android.telephony.CellBroadcastService#onCdmaCellBroadcastSms(int, byte[]) parameter #1:
+    
+MissingNullability: android.telephony.CellBroadcastService#onCdmaCellBroadcastSms(int, byte[], int) parameter #1:
+    
+MissingNullability: android.telephony.CellBroadcastService#onGsmCellBroadcastSms(int, byte[]) parameter #1:
+    
+MissingNullability: android.telephony.ModemActivityInfo#toString():
+    
+MissingNullability: android.telephony.ModemActivityInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+    
+MissingNullability: android.telephony.ModemActivityInfo.TransmitPower#toString():
+    
+MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
+    
+MissingNullability: android.telephony.SmsCbCmasInfo#toString():
+    
+MissingNullability: android.telephony.SmsCbCmasInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+    
+MissingNullability: android.telephony.SmsCbEtwsInfo#toString():
+    
+MissingNullability: android.telephony.SmsCbEtwsInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+    
+MissingNullability: android.telephony.SmsCbLocation#equals(Object) parameter #0:
+    
+MissingNullability: android.telephony.SmsCbLocation#toString():
+    
+MissingNullability: android.telephony.SmsCbLocation#writeToParcel(android.os.Parcel, int) parameter #0:
+    
+MissingNullability: android.telephony.SmsCbMessage#toString():
+    
+MissingNullability: android.telephony.SmsCbMessage#writeToParcel(android.os.Parcel, int) parameter #0:
+    
+MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringDaily(java.time.ZonedDateTime) parameter #0:
+    
+MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringMonthly(java.time.ZonedDateTime) parameter #0:
+    
+MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringWeekly(java.time.ZonedDateTime) parameter #0:
+    
+MissingNullability: android.telephony.cdma.CdmaSmsCbProgramData#toString():
+    
+MissingNullability: android.telephony.cdma.CdmaSmsCbProgramData#writeToParcel(android.os.Parcel, int) parameter #0:
+    
+MissingNullability: android.telephony.data.DataService#onUnbind(android.content.Intent) parameter #0:
+    
+MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, String, byte[]) parameter #1:
+    
+MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, String, byte[]) parameter #2:
+    
+MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String):
+    
+MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String) parameter #0:
+    
+
+
+NoClone: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
+    
+
+
+ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context):
+    
+ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
+    
+ProtectedMember: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context):
+    
+
+
+SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean):
+    
+SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean, String[]):
+    
+SamShouldBeLast: android.accounts.AccountManager#confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#editProperties(String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, android.os.Bundle, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#getAuthTokenByFeatures(String, String, String[], android.app.Activity, android.os.Bundle, android.os.Bundle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#isCredentialsUpdateSuggested(android.accounts.Account, String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#renameAccount(android.accounts.Account, String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#startAddAccountSession(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#startUpdateCredentialsSession(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+    
+SamShouldBeLast: android.accounts.AccountManager#updateCredentials(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+    
+SamShouldBeLast: android.app.AlarmManager#set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
+    
+SamShouldBeLast: android.app.AlarmManager#setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
+    
+SamShouldBeLast: android.app.AlarmManager#setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
+    
+SamShouldBeLast: android.app.WallpaperInfo#dump(android.util.Printer, String):
+    
+SamShouldBeLast: android.app.admin.DevicePolicyManager#installSystemUpdate(android.content.ComponentName, android.net.Uri, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback):
+    
+SamShouldBeLast: android.content.Context#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection):
+    
+SamShouldBeLast: android.content.Context#bindService(android.content.Intent, int, java.util.concurrent.Executor, android.content.ServiceConnection):
+    
+SamShouldBeLast: android.content.ContextWrapper#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection):
+    
+SamShouldBeLast: android.content.ContextWrapper#bindService(android.content.Intent, int, java.util.concurrent.Executor, android.content.ServiceConnection):
+    
+SamShouldBeLast: android.content.IntentFilter#dump(android.util.Printer, String):
+    
+SamShouldBeLast: android.content.pm.ApplicationInfo#dump(android.util.Printer, String):
+    
+SamShouldBeLast: android.content.pm.LauncherApps#registerPackageInstallerSessionCallback(java.util.concurrent.Executor, android.content.pm.PackageInstaller.SessionCallback):
+    
+SamShouldBeLast: android.content.pm.PackageItemInfo#dumpBack(android.util.Printer, String):
+    
+SamShouldBeLast: android.content.pm.PackageItemInfo#dumpFront(android.util.Printer, String):
+    
+SamShouldBeLast: android.content.pm.ResolveInfo#dump(android.util.Printer, String):
+    
+SamShouldBeLast: android.location.Location#dump(android.util.Printer, String):
+    
+SamShouldBeLast: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler):
+    
+SamShouldBeLast: android.location.LocationManager#registerGnssMeasurementsCallback(java.util.concurrent.Executor, android.location.GnssMeasurementsEvent.Callback):
+    
+SamShouldBeLast: android.location.LocationManager#registerGnssNavigationMessageCallback(java.util.concurrent.Executor, android.location.GnssNavigationMessage.Callback):
+    
+SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
+    
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener):
+    
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener):
+    
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener):
+    
+SamShouldBeLast: android.media.AudioFocusRequest.Builder#setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler):
+    
+SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int):
+    
+SamShouldBeLast: android.media.AudioRecord#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+    
+SamShouldBeLast: android.media.AudioRecord#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
+    
+SamShouldBeLast: android.media.AudioRecordingMonitor#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
+    
+SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+    
+SamShouldBeLast: android.media.MediaRecorder#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+    
+SamShouldBeLast: android.media.MediaRecorder#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
+    
+SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName):
+    
+SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler):
+    
+SamShouldBeLast: android.media.session.MediaSessionManager#addOnSession2TokensChangedListener(android.media.session.MediaSessionManager.OnSession2TokensChangedListener, android.os.Handler):
+    
+SamShouldBeLast: android.media.session.MediaSessionManager#registerCallback(java.util.concurrent.Executor, android.media.session.MediaSessionManager.Callback):
+    
+SamShouldBeLast: android.net.ConnectivityManager#createSocketKeepalive(android.net.Network, android.net.IpSecManager.UdpEncapsulationSocket, java.net.InetAddress, java.net.InetAddress, java.util.concurrent.Executor, android.net.SocketKeepalive.Callback):
+    
+SamShouldBeLast: android.net.wifi.rtt.WifiRttManager#startRanging(android.net.wifi.rtt.RangingRequest, java.util.concurrent.Executor, android.net.wifi.rtt.RangingResultCallback):
+    
+SamShouldBeLast: android.nfc.NfcAdapter#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle):
+    
+SamShouldBeLast: android.nfc.NfcAdapter#ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler):
+    
+SamShouldBeLast: android.nfc.NfcAdapter#setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity):
+    
+SamShouldBeLast: android.nfc.NfcAdapter#setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...):
+    
+SamShouldBeLast: android.nfc.NfcAdapter#setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...):
+    
+SamShouldBeLast: android.os.Binder#attachInterface(android.os.IInterface, String):
+    
+SamShouldBeLast: android.os.Binder#linkToDeath(android.os.IBinder.DeathRecipient, int):
+    
+SamShouldBeLast: android.os.Binder#unlinkToDeath(android.os.IBinder.DeathRecipient, int):
+    
+SamShouldBeLast: android.os.Handler#dump(android.util.Printer, String):
+    
+SamShouldBeLast: android.os.Handler#postAtTime(Runnable, Object, long):
+    
+SamShouldBeLast: android.os.Handler#postAtTime(Runnable, long):
+    
+SamShouldBeLast: android.os.Handler#postDelayed(Runnable, Object, long):
+    
+SamShouldBeLast: android.os.Handler#postDelayed(Runnable, long):
+    
+SamShouldBeLast: android.os.Handler#removeCallbacks(Runnable, Object):
+    
+SamShouldBeLast: android.os.IBinder#linkToDeath(android.os.IBinder.DeathRecipient, int):
+    
+SamShouldBeLast: android.os.IBinder#unlinkToDeath(android.os.IBinder.DeathRecipient, int):
+    
+SamShouldBeLast: android.os.RecoverySystem#verifyPackage(java.io.File, android.os.RecoverySystem.ProgressListener, java.io.File):
+    
+SamShouldBeLast: android.telephony.MbmsDownloadSession#addProgressListener(android.telephony.mbms.DownloadRequest, java.util.concurrent.Executor, android.telephony.mbms.DownloadProgressListener):
+    
+SamShouldBeLast: android.telephony.MbmsDownloadSession#addStatusListener(android.telephony.mbms.DownloadRequest, java.util.concurrent.Executor, android.telephony.mbms.DownloadStatusListener):
+    
+SamShouldBeLast: android.telephony.MbmsDownloadSession#create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsDownloadSessionCallback):
+    
+SamShouldBeLast: android.telephony.MbmsDownloadSession#create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsDownloadSessionCallback):
+    
+SamShouldBeLast: android.telephony.MbmsGroupCallSession#create(android.content.Context, int, java.util.concurrent.Executor, android.telephony.mbms.MbmsGroupCallSessionCallback):
+    
+SamShouldBeLast: android.telephony.MbmsGroupCallSession#create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsGroupCallSessionCallback):
+    
+SamShouldBeLast: android.telephony.MbmsGroupCallSession#startGroupCall(long, java.util.List<java.lang.Integer>, java.util.List<java.lang.Integer>, java.util.concurrent.Executor, android.telephony.mbms.GroupCallCallback):
+    
+SamShouldBeLast: android.telephony.MbmsStreamingSession#create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsStreamingSessionCallback):
+    
+SamShouldBeLast: android.telephony.MbmsStreamingSession#create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsStreamingSessionCallback):
+    
+SamShouldBeLast: android.telephony.MbmsStreamingSession#startStreaming(android.telephony.mbms.StreamingServiceInfo, java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback):
+    
+SamShouldBeLast: android.telephony.SmsManager#getSmsMessagesForFinancialApp(android.os.Bundle, java.util.concurrent.Executor, android.telephony.SmsManager.FinancialSmsCallback):
+    
+SamShouldBeLast: android.telephony.SubscriptionManager#addOnOpportunisticSubscriptionsChangedListener(java.util.concurrent.Executor, android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener):
+    
+SamShouldBeLast: android.telephony.TelephonyManager#requestCellInfoUpdate(java.util.concurrent.Executor, android.telephony.TelephonyManager.CellInfoCallback):
+    
+SamShouldBeLast: android.telephony.TelephonyManager#requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback):
+    
+SamShouldBeLast: android.telephony.TelephonyManager#setPreferredOpportunisticDataSubscription(int, boolean, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
+    
+SamShouldBeLast: android.telephony.TelephonyManager#updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
+    
+SamShouldBeLast: android.view.View#postDelayed(Runnable, long):
+    
+SamShouldBeLast: android.view.View#postOnAnimationDelayed(Runnable, long):
+    
+SamShouldBeLast: android.view.View#scheduleDrawable(android.graphics.drawable.Drawable, Runnable, long):
+    
+SamShouldBeLast: android.view.Window#addOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener, android.os.Handler):
+    
+SamShouldBeLast: android.view.accessibility.AccessibilityManager#addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, android.os.Handler):
+    
+SamShouldBeLast: android.view.accessibility.AccessibilityManager#addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, android.os.Handler):
+    
+SamShouldBeLast: android.webkit.WebChromeClient#onShowFileChooser(android.webkit.WebView, android.webkit.ValueCallback<android.net.Uri[]>, android.webkit.WebChromeClient.FileChooserParams):
+    
+SamShouldBeLast: android.webkit.WebView#setWebViewRenderProcessClient(java.util.concurrent.Executor, android.webkit.WebViewRenderProcessClient):
+    
+
+
+ServiceName: android.Manifest.permission#BIND_ATTENTION_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_AUGMENTED_AUTOFILL_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_CELL_BROADCAST_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_CONTENT_CAPTURE_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_CONTENT_SUGGESTIONS_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_EUICC_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_EXTERNAL_STORAGE_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_IMS_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_NETWORK_RECOMMENDATION_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_NOTIFICATION_ASSISTANT_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_PRINT_RECOMMENDATION_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_RESOLVER_RANKER_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_SETTINGS_SUGGESTIONS_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_SOUND_TRIGGER_DETECTION_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_TELEPHONY_DATA_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_TELEPHONY_NETWORK_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_TEXTCLASSIFIER_SERVICE:
+    
+ServiceName: android.Manifest.permission#BIND_TV_REMOTE_SERVICE:
+    
+ServiceName: android.Manifest.permission#PROVIDE_RESOLVER_RANKER_SERVICE:
+    
+ServiceName: android.Manifest.permission#REQUEST_NOTIFICATION_ASSISTANT_SERVICE:
+    
+ServiceName: android.provider.DeviceConfig#NAMESPACE_PACKAGE_MANAGER_SERVICE:
+    
diff --git a/api/test-current.txt b/api/test-current.txt
index 700be90..2c7e44d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -659,7 +659,7 @@
   }
 
   public abstract class Context {
-    method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle);
+    method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
     method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.view.Display getDisplay();
     method public abstract int getDisplayId();
@@ -731,6 +731,7 @@
     method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS", "android.permission.GET_RUNTIME_PERMISSIONS"}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
     method @NonNull public abstract String getSharedSystemSharedLibraryPackageName();
+    method @Nullable public String[] getTelephonyPackageNames();
     method @Nullable public String getWellbeingPackageName();
     method @RequiresPermission("android.permission.GRANT_RUNTIME_PERMISSIONS") public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
@@ -769,8 +770,10 @@
     field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
     field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
     field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
+    field public static final int PROTECTION_FLAG_TELEPHONY = 4194304; // 0x400000
     field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
     field public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000
+    field public static final int PROTECTION_FLAG_WIFI = 8388608; // 0x800000
     field @Nullable public final String backgroundPermission;
   }
 
@@ -788,7 +791,9 @@
 
   public final class AssetManager implements java.lang.AutoCloseable {
     method @NonNull public String[] getApkPaths();
+    method @Nullable public String getLastResourceResolution();
     method @Nullable public String getOverlayablesToString(String);
+    method public void setResourceResolutionLoggingEnabled(boolean);
   }
 
   public final class Configuration implements java.lang.Comparable<android.content.res.Configuration> android.os.Parcelable {
@@ -1089,11 +1094,14 @@
 
   public class Location implements android.os.Parcelable {
     method public void makeComplete();
+    method public void setExtraLocation(@Nullable String, @Nullable android.location.Location);
+    field public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
   }
 
   public class LocationManager {
     method @NonNull public String[] getBackgroundThrottlingWhitelist();
     method @NonNull public String[] getIgnoreSettingsWhitelist();
+    method @Nullable @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public java.util.List<java.lang.String> getProviderPackages(@NonNull String);
     method @NonNull public java.util.List<android.location.LocationRequest> getTestProviderCurrentRequests(String);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
@@ -1741,6 +1749,7 @@
   }
 
   public class DeviceIdleManager {
+    method @RequiresPermission("android.permission.DEVICE_POWER") public int addPowerSaveWhitelistApps(@NonNull java.util.List<java.lang.String>);
     method @NonNull public String[] getSystemPowerWhitelist();
     method @NonNull public String[] getSystemPowerWhitelistExceptIdle();
   }
@@ -2244,8 +2253,11 @@
 package android.permission {
 
   public final class PermissionControllerManager {
+    method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.RESTORE_RUNTIME_PERMISSIONS"}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler);
+    method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
     method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
+    method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.RESTORE_RUNTIME_PERMISSIONS"}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
     field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
     field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
     field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index cb27325..8af925a 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -230,6 +230,7 @@
         "tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
         "tests/e2e/Attribution_e2e_test.cpp",
         "tests/e2e/ConfigTtl_e2e_test.cpp",
+        "tests/e2e/CountMetric_e2e_test.cpp",
         "tests/e2e/DurationMetric_e2e_test.cpp",
         "tests/e2e/GaugeMetric_e2e_pull_test.cpp",
         "tests/e2e/GaugeMetric_e2e_push_test.cpp",
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index ff7416c..6c3dff2 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -16,24 +16,26 @@
 
 #define DEBUG false  // STOPSHIP if true
 #include "Log.h"
-#include "statslog.h"
+
+#include "StatsLogProcessor.h"
 
 #include <android-base/file.h>
 #include <dirent.h>
 #include <frameworks/base/cmds/statsd/src/active_config_list.pb.h>
-#include "StatsLogProcessor.h"
+#include <log/log_event_list.h>
+#include <utils/Errors.h>
+#include <utils/SystemClock.h>
+
 #include "android-base/stringprintf.h"
 #include "external/StatsPullerManager.h"
 #include "guardrail/StatsdStats.h"
 #include "metrics/CountMetricProducer.h"
+#include "state/StateManager.h"
 #include "stats_log_util.h"
 #include "stats_util.h"
+#include "statslog.h"
 #include "storage/StorageManager.h"
 
-#include <log/log_event_list.h>
-#include <utils/Errors.h>
-#include <utils/SystemClock.h>
-
 using namespace android;
 using android::base::StringPrintf;
 using android::util::FIELD_COUNT_REPEATED;
@@ -218,6 +220,8 @@
         onIsolatedUidChangedEventLocked(*event);
     }
 
+    StateManager::getInstance().onLogEvent(*event);
+
     if (mMetricsManagers.empty()) {
         return;
     }
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 3d002d2..8292a3a 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -262,6 +262,10 @@
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
 
+    FRIEND_TEST(CountMetricE2eTest, TestWithSimpleState);
+    FRIEND_TEST(CountMetricE2eTest, TestWithMappedState);
+    FRIEND_TEST(CountMetricE2eTest, TestWithMultipleStates);
+
     FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
     FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
diff --git a/cmds/statsd/src/anomaly/subscriber_util.cpp b/cmds/statsd/src/anomaly/subscriber_util.cpp
index e09d575..4c30c4c 100644
--- a/cmds/statsd/src/anomaly/subscriber_util.cpp
+++ b/cmds/statsd/src/anomaly/subscriber_util.cpp
@@ -40,7 +40,7 @@
 
     for (const Subscription& subscription : subscriptions) {
         if (subscription.probability_of_informing() < 1
-                && ((float)rand() / RAND_MAX) >= subscription.probability_of_informing()) {
+                && ((float)rand() / (float)RAND_MAX) >= subscription.probability_of_informing()) {
             // Note that due to float imprecision, 0.0 and 1.0 might not truly mean never/always.
             // The config writer was advised to use -0.1 and 1.1 for never/always.
             ALOGI("Fate decided that a subscriber would not be informed.");
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 43e33f5..5a76d1f 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -122,9 +122,10 @@
          {.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
         // system_elapsed_realtime
         {android::util::SYSTEM_ELAPSED_REALTIME,
-         {.pullTimeoutNs = NS_PER_SEC / 2,
-          .coolDownNs = NS_PER_SEC,
-          .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME)}},
+         {.coolDownNs = NS_PER_SEC,
+          .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME),
+          .pullTimeoutNs = NS_PER_SEC / 2,
+         }},
         // system_uptime
         {android::util::SYSTEM_UPTIME,
          {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index b5c8e35..4a06387 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -18,13 +18,15 @@
 #include "Log.h"
 
 #include "CountMetricProducer.h"
-#include "guardrail/StatsdStats.h"
-#include "stats_util.h"
-#include "stats_log_util.h"
 
+#include <inttypes.h>
 #include <limits.h>
 #include <stdlib.h>
 
+#include "guardrail/StatsdStats.h"
+#include "stats_log_util.h"
+#include "stats_util.h"
+
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
 using android::util::FIELD_TYPE_FLOAT;
@@ -65,16 +67,16 @@
 const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5;
 const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6;
 
-CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric& metric,
-                                         const int conditionIndex,
-                                         const sp<ConditionWizard>& wizard,
-                                         const int64_t timeBaseNs, const int64_t startTimeNs,
-                                         const unordered_map<int, shared_ptr<Activation>>&
-                                                 eventActivationMap,
-                                         const unordered_map<int, vector<shared_ptr<Activation>>>&
-                                                 eventDeactivationMap)
+CountMetricProducer::CountMetricProducer(
+        const ConfigKey& key, const CountMetric& metric, const int conditionIndex,
+        const sp<ConditionWizard>& wizard, const int64_t timeBaseNs, const int64_t startTimeNs,
+
+        const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+        const vector<int>& slicedStateAtoms,
+        const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
     : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard, eventActivationMap,
-                     eventDeactivationMap) {
+                     eventDeactivationMap, slicedStateAtoms, stateGroupMap) {
     if (metric.has_bucket()) {
         mBucketSizeNs =
                 TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000;
@@ -100,6 +102,8 @@
         mConditionSliced = true;
     }
 
+    // TODO(tsaichristine): b/142124705 handle metric state links
+
     flushIfNeededLocked(startTimeNs);
     // Adjust start for partial bucket
     mCurrentBucketStartTimeNs = startTimeNs;
@@ -112,6 +116,12 @@
     VLOG("~CountMetricProducer() called");
 }
 
+void CountMetricProducer::onStateChanged(int atomId, const HashableDimensionKey& primaryKey,
+                                         int oldState, int newState) {
+    VLOG("CountMetric %lld onStateChanged State%d, key %s, %d -> %d", (long long)mMetricId, atomId,
+         primaryKey.toString().c_str(), oldState, newState);
+}
+
 void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
     if (mCurrentSlicedCounter == nullptr ||
         mCurrentSlicedCounter->size() == 0) {
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 61913c7..61e0892 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -17,15 +17,16 @@
 #ifndef COUNT_METRIC_PRODUCER_H
 #define COUNT_METRIC_PRODUCER_H
 
-#include <unordered_map>
-
 #include <android/util/ProtoOutputStream.h>
 #include <gtest/gtest_prod.h>
-#include "../anomaly/AnomalyTracker.h"
-#include "../condition/ConditionTracker.h"
-#include "../matchers/matcher_util.h"
+
+#include <unordered_map>
+
 #include "MetricProducer.h"
+#include "anomaly/AnomalyTracker.h"
+#include "condition/ConditionTracker.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "matchers/matcher_util.h"
 #include "stats_util.h"
 
 namespace android {
@@ -40,16 +41,20 @@
 
 class CountMetricProducer : public MetricProducer {
 public:
-    CountMetricProducer(const ConfigKey& key, const CountMetric& countMetric,
-                        const int conditionIndex, const sp<ConditionWizard>& wizard,
-                        const int64_t timeBaseNs, const int64_t startTimeNs,
-                        const std::unordered_map<int, std::shared_ptr<Activation>>&
-                                eventActivationMap = {},
-                        const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
-                                eventDeactivationMap = {});
+    CountMetricProducer(
+            const ConfigKey& key, const CountMetric& countMetric, const int conditionIndex,
+            const sp<ConditionWizard>& wizard, const int64_t timeBaseNs, const int64_t startTimeNs,
+            const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+            const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+                    eventDeactivationMap = {},
+            const vector<int>& slicedStateAtoms = {},
+            const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
 
     virtual ~CountMetricProducer();
 
+    void onStateChanged(int atomId, const HashableDimensionKey& primaryKey, int oldState,
+                        int newState) override;
+
 protected:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const MetricDimensionKey& eventKey,
@@ -106,6 +111,7 @@
     FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgrade);
     FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket);
     FRIEND_TEST(CountMetricProducerTest, TestFirstBucket);
+    FRIEND_TEST(CountMetricProducerTest, TestOneWeekTimeUnit);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 31b90f3..ab2a1c3 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -69,9 +69,11 @@
         const bool nesting, const sp<ConditionWizard>& wizard,
         const FieldMatcher& internalDimensions, const int64_t timeBaseNs, const int64_t startTimeNs,
         const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
-        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap)
+        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+        const vector<int>& slicedStateAtoms,
+        const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
     : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard, eventActivationMap,
-                     eventDeactivationMap),
+                     eventDeactivationMap, slicedStateAtoms, stateGroupMap),
       mAggregationType(metric.aggregation_type()),
       mStartIndex(startIndex),
       mStopIndex(stopIndex),
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 0592b18..7457d7f 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -38,16 +38,16 @@
 
 class DurationMetricProducer : public MetricProducer {
 public:
-    DurationMetricProducer(const ConfigKey& key, const DurationMetric& durationMetric,
-                           const int conditionIndex, const size_t startIndex,
-                           const size_t stopIndex, const size_t stopAllIndex, const bool nesting,
-                           const sp<ConditionWizard>& wizard,
-                           const FieldMatcher& internalDimensions, const int64_t timeBaseNs,
-                           const int64_t startTimeNs,
-                           const unordered_map<int, shared_ptr<Activation>>&
-                                   eventActivationMap = {},
-                           const unordered_map<int, vector<shared_ptr<Activation>>>&
-                                   eventDeactivationMap = {});
+    DurationMetricProducer(
+            const ConfigKey& key, const DurationMetric& durationMetric, const int conditionIndex,
+            const size_t startIndex, const size_t stopIndex, const size_t stopAllIndex,
+            const bool nesting, const sp<ConditionWizard>& wizard,
+            const FieldMatcher& internalDimensions, const int64_t timeBaseNs,
+            const int64_t startTimeNs,
+            const unordered_map<int, shared_ptr<Activation>>& eventActivationMap = {},
+            const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap = {},
+            const vector<int>& slicedStateAtoms = {},
+            const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
 
     virtual ~DurationMetricProducer();
 
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index a60a916..32eb077 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -52,16 +52,15 @@
 const int FIELD_ID_ELAPSED_TIMESTAMP_NANOS = 1;
 const int FIELD_ID_ATOMS = 2;
 
-EventMetricProducer::EventMetricProducer(const ConfigKey& key, const EventMetric& metric,
-                                         const int conditionIndex,
-                                         const sp<ConditionWizard>& wizard,
-                                         const int64_t startTimeNs,
-                                         const unordered_map<int, shared_ptr<Activation>>&
-                                                 eventActivationMap,
-                                         const unordered_map<int, vector<shared_ptr<Activation>>>&
-                                                 eventDeactivationMap)
+EventMetricProducer::EventMetricProducer(
+        const ConfigKey& key, const EventMetric& metric, const int conditionIndex,
+        const sp<ConditionWizard>& wizard, const int64_t startTimeNs,
+        const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+        const vector<int>& slicedStateAtoms,
+        const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
     : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard, eventActivationMap,
-                     eventDeactivationMap) {
+                     eventDeactivationMap, slicedStateAtoms, stateGroupMap) {
     if (metric.links().size() > 0) {
         for (const auto& link : metric.links()) {
             Metric2Condition mc;
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index aab53c8..dca37e8 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -33,13 +33,14 @@
 
 class EventMetricProducer : public MetricProducer {
 public:
-    EventMetricProducer(const ConfigKey& key, const EventMetric& eventMetric,
-                        const int conditionIndex, const sp<ConditionWizard>& wizard,
-                        const int64_t startTimeNs,
-                        const std::unordered_map<int, std::shared_ptr<Activation>>&
-                                eventActivationMap = {},
-                        const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
-                                eventDeactivationMap = {});
+    EventMetricProducer(
+            const ConfigKey& key, const EventMetric& eventMetric, const int conditionIndex,
+            const sp<ConditionWizard>& wizard, const int64_t startTimeNs,
+            const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+            const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+                    eventDeactivationMap = {},
+            const vector<int>& slicedStateAtoms = {},
+            const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
 
     virtual ~EventMetricProducer();
 
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index e409b6fb..d0f88a8 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -74,9 +74,11 @@
         const int atomId, const int64_t timeBaseNs, const int64_t startTimeNs,
         const sp<StatsPullerManager>& pullerManager,
         const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
-        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap)
+        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+        const vector<int>& slicedStateAtoms,
+        const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
     : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard, eventActivationMap,
-            eventDeactivationMap),
+                     eventDeactivationMap, slicedStateAtoms, stateGroupMap),
       mWhatMatcherIndex(whatMatcherIndex),
       mEventMatcherWizard(matcherWizard),
       mPullerManager(pullerManager),
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index dfe1d56..640a02a 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -56,16 +56,17 @@
 // producer always reports the guage at the earliest time of the bucket when the condition is met.
 class GaugeMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
 public:
-    GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric,
-                        const int conditionIndex, const sp<ConditionWizard>& conditionWizard,
-                        const int whatMatcherIndex,const sp<EventMatcherWizard>& matcherWizard,
-                        const int pullTagId, const int triggerAtomId, const int atomId,
-                        const int64_t timeBaseNs, const int64_t startTimeNs,
-                        const sp<StatsPullerManager>& pullerManager,
-                        const std::unordered_map<int, std::shared_ptr<Activation>>&
-                                eventActivationMap = {},
-                        const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
-                                eventDeactivationMap = {});
+    GaugeMetricProducer(
+            const ConfigKey& key, const GaugeMetric& gaugeMetric, const int conditionIndex,
+            const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex,
+            const sp<EventMatcherWizard>& matcherWizard, const int pullTagId,
+            const int triggerAtomId, const int atomId, const int64_t timeBaseNs,
+            const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager,
+            const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+            const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+                    eventDeactivationMap = {},
+            const vector<int>& slicedStateAtoms = {},
+            const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
 
     virtual ~GaugeMetricProducer();
 
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 3426a19..2a700ef 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -45,23 +45,27 @@
         const int conditionIndex, const sp<ConditionWizard>& wizard,
         const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap,
         const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
-                eventDeactivationMap)
-        : mMetricId(metricId),
-          mConfigKey(key),
-          mTimeBaseNs(timeBaseNs),
-          mCurrentBucketStartTimeNs(timeBaseNs),
-          mCurrentBucketNum(0),
-          mCondition(initialCondition(conditionIndex)),
-          mConditionTrackerIndex(conditionIndex),
-          mConditionSliced(false),
-          mWizard(wizard),
-          mContainANYPositionInDimensionsInWhat(false),
-          mSliceByPositionALL(false),
-          mHasLinksToAllConditionDimensionsInTracker(false),
-          mEventActivationMap(eventActivationMap),
-          mEventDeactivationMap(eventDeactivationMap),
-          mIsActive(mEventActivationMap.empty()) {
-    }
+                eventDeactivationMap,
+        const vector<int>& slicedStateAtoms,
+        const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+    : mMetricId(metricId),
+      mConfigKey(key),
+      mTimeBaseNs(timeBaseNs),
+      mCurrentBucketStartTimeNs(timeBaseNs),
+      mCurrentBucketNum(0),
+      mCondition(initialCondition(conditionIndex)),
+      mConditionTrackerIndex(conditionIndex),
+      mConditionSliced(false),
+      mWizard(wizard),
+      mContainANYPositionInDimensionsInWhat(false),
+      mSliceByPositionALL(false),
+      mHasLinksToAllConditionDimensionsInTracker(false),
+      mEventActivationMap(eventActivationMap),
+      mEventDeactivationMap(eventDeactivationMap),
+      mIsActive(mEventActivationMap.empty()),
+      mSlicedStateAtoms(slicedStateAtoms),
+      mStateGroupMap(stateGroupMap) {
+}
 
 void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) {
     if (!mIsActive) {
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 1e1eb69..a72de22 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -17,19 +17,19 @@
 #ifndef METRIC_PRODUCER_H
 #define METRIC_PRODUCER_H
 
-#include <shared_mutex>
-
 #include <frameworks/base/cmds/statsd/src/active_config_list.pb.h>
+#include <log/logprint.h>
+#include <utils/RefBase.h>
+
+#include <unordered_map>
+
 #include "HashableDimensionKey.h"
 #include "anomaly/AnomalyTracker.h"
 #include "condition/ConditionWizard.h"
 #include "config/ConfigKey.h"
 #include "matchers/matcher_util.h"
 #include "packages/PackageInfoListener.h"
-
-#include <log/logprint.h>
-#include <utils/RefBase.h>
-#include <unordered_map>
+#include "state/StateListener.h"
 
 namespace android {
 namespace os {
@@ -86,13 +86,15 @@
 // writing the report to dropbox. MetricProducers should respond to package changes as required in
 // PackageInfoListener, but if none of the metrics are slicing by package name, then the update can
 // be a no-op.
-class MetricProducer : public virtual PackageInfoListener {
+class MetricProducer : public virtual PackageInfoListener, public virtual StateListener {
 public:
     MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs,
                    const int conditionIndex, const sp<ConditionWizard>& wizard,
                    const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap,
                    const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
-                           eventDeactivationMap);
+                           eventDeactivationMap,
+                   const vector<int>& slicedStateAtoms,
+                   const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap);
 
     virtual ~MetricProducer(){};
 
@@ -151,6 +153,9 @@
         return mConditionSliced;
     };
 
+    void onStateChanged(int atomId, const HashableDimensionKey& primaryKey, int oldState,
+                        int newState){};
+
     // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
     // This method clears all the past buckets.
     void onDumpReport(const int64_t dumpTimeNs,
@@ -230,6 +235,11 @@
         return mBucketSizeNs;
     }
 
+    inline const std::vector<int> getSlicedStateAtoms() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mSlicedStateAtoms;
+    }
+
     /* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */
     virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
                                                  const sp<AlarmMonitor>& anomalyAlarmMonitor) {
@@ -381,6 +391,16 @@
 
     bool mIsActive;
 
+    // The slice_by_state atom ids defined in statsd_config.
+    std::vector<int> mSlicedStateAtoms;
+
+    // Maps atom ids and state values to group_ids (<atom_id, <value, group_id>>).
+    std::unordered_map<int, std::unordered_map<int, int64_t>> mStateGroupMap;
+
+    FRIEND_TEST(CountMetricE2eTest, TestWithSimpleState);
+    FRIEND_TEST(CountMetricE2eTest, TestWithMappedState);
+    FRIEND_TEST(CountMetricE2eTest, TestWithMultipleStates);
+
     FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
     FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 963205e..7bae4b9 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -15,8 +15,12 @@
  */
 #define DEBUG false  // STOPSHIP if true
 #include "Log.h"
+
 #include "MetricsManager.h"
-#include "statslog.h"
+
+#include <log/logprint.h>
+#include <private/android_filesystem_config.h>
+#include <utils/SystemClock.h>
 
 #include "CountMetricProducer.h"
 #include "condition/CombinationConditionTracker.h"
@@ -25,12 +29,10 @@
 #include "matchers/CombinationLogMatchingTracker.h"
 #include "matchers/SimpleLogMatchingTracker.h"
 #include "metrics_manager_util.h"
-#include "stats_util.h"
+#include "state/StateManager.h"
 #include "stats_log_util.h"
-
-#include <log/logprint.h>
-#include <private/android_filesystem_config.h>
-#include <utils/SystemClock.h>
+#include "stats_util.h"
+#include "statslog.h"
 
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_INT32;
@@ -149,6 +151,12 @@
 }
 
 MetricsManager::~MetricsManager() {
+    for (auto it : mAllMetricProducers) {
+        for (int atomId : it->getSlicedStateAtoms()) {
+            StateManager::getInstance().unregisterListener(atomId, it);
+        }
+    }
+
     VLOG("~MetricsManager()");
 }
 
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 3dad614..d184121 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -282,6 +282,10 @@
             TestActivationOnBootMultipleActivationsDifferentActivationTypes);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
 
+    FRIEND_TEST(CountMetricE2eTest, TestWithSimpleState);
+    FRIEND_TEST(CountMetricE2eTest, TestWithMappedState);
+    FRIEND_TEST(CountMetricE2eTest, TestWithMultipleStates);
+
     FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
     FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 7fe5a83..6fd0327 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -83,9 +83,11 @@
         const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, const int64_t timeBaseNs,
         const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager,
         const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
-        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap)
+        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+        const vector<int>& slicedStateAtoms,
+        const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
     : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, conditionWizard,
-                     eventActivationMap, eventDeactivationMap),
+                     eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap),
       mWhatMatcherIndex(whatMatcherIndex),
       mEventMatcherWizard(matcherWizard),
       mPullerManager(pullerManager),
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index d7cd397..206e602 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -52,15 +52,17 @@
 // - an alarm set to the end of the bucket
 class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
 public:
-    ValueMetricProducer(const ConfigKey& key, const ValueMetric& valueMetric,
-                        const int conditionIndex, const sp<ConditionWizard>& conditionWizard,
-                        const int whatMatcherIndex, const sp<EventMatcherWizard>& matcherWizard,
-                        const int pullTagId, const int64_t timeBaseNs, const int64_t startTimeNs,
-                        const sp<StatsPullerManager>& pullerManager,
-                        const std::unordered_map<int, std::shared_ptr<Activation>>&
-                                eventActivationMap = {},
-                        const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
-                                eventDeactivationMap = {});
+    ValueMetricProducer(
+            const ConfigKey& key, const ValueMetric& valueMetric, const int conditionIndex,
+            const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex,
+            const sp<EventMatcherWizard>& matcherWizard, const int pullTagId,
+            const int64_t timeBaseNs, const int64_t startTimeNs,
+            const sp<StatsPullerManager>& pullerManager,
+            const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+            const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+                    eventDeactivationMap = {},
+            const vector<int>& slicedStateAtoms = {},
+            const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
 
     virtual ~ValueMetricProducer();
 
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 0fee71e..33e162e 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -20,24 +20,24 @@
 #include "metrics_manager_util.h"
 #include "MetricProducer.h"
 
-#include "../condition/CombinationConditionTracker.h"
-#include "../condition/SimpleConditionTracker.h"
-#include "../condition/StateConditionTracker.h"
-#include "../external/StatsPullerManager.h"
-#include "../matchers/CombinationLogMatchingTracker.h"
-#include "../matchers/SimpleLogMatchingTracker.h"
-#include "../matchers/EventMatcherWizard.h"
-#include "../metrics/CountMetricProducer.h"
-#include "../metrics/DurationMetricProducer.h"
-#include "../metrics/EventMetricProducer.h"
-#include "../metrics/GaugeMetricProducer.h"
-#include "../metrics/ValueMetricProducer.h"
+#include <inttypes.h>
 
+#include "condition/CombinationConditionTracker.h"
+#include "condition/SimpleConditionTracker.h"
+#include "condition/StateConditionTracker.h"
+#include "external/StatsPullerManager.h"
+#include "matchers/CombinationLogMatchingTracker.h"
+#include "matchers/EventMatcherWizard.h"
+#include "matchers/SimpleLogMatchingTracker.h"
+#include "metrics/CountMetricProducer.h"
+#include "metrics/DurationMetricProducer.h"
+#include "metrics/EventMetricProducer.h"
+#include "metrics/GaugeMetricProducer.h"
+#include "metrics/ValueMetricProducer.h"
+#include "state/StateManager.h"
 #include "stats_util.h"
 #include "statslog.h"
 
-#include <inttypes.h>
-
 using std::set;
 using std::string;
 using std::unordered_map;
@@ -138,6 +138,41 @@
     return true;
 }
 
+// Initializes state data structures for a metric.
+// input:
+// [config]: the input config
+// [stateIds]: the slice_by_state ids for this metric
+// [stateAtomIdMap]: this map contains the mapping from all state ids to atom ids
+// [allStateGroupMaps]: this map contains the mapping from state ids and state
+//                      values to state group ids for all states
+// output:
+// [slicedStateAtoms]: a vector of atom ids of all the slice_by_states
+// [stateGroupMap]: this map should contain the mapping from states ids and state
+//                      values to state group ids for all states that this metric
+//                      is interested in
+bool handleMetricWithStates(
+        const StatsdConfig& config, const ::google::protobuf::RepeatedField<int64_t>& stateIds,
+        const unordered_map<int64_t, int>& stateAtomIdMap,
+        const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
+        vector<int>& slicedStateAtoms,
+        unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap) {
+    for (const auto& stateId : stateIds) {
+        auto it = stateAtomIdMap.find(stateId);
+        if (it == stateAtomIdMap.end()) {
+            ALOGW("cannot find State %" PRId64 " in the config", stateId);
+            return false;
+        }
+        int atomId = it->second;
+        slicedStateAtoms.push_back(atomId);
+
+        auto stateIt = allStateGroupMaps.find(stateId);
+        if (stateIt != allStateGroupMaps.end()) {
+            stateGroupMap[atomId] = stateIt->second;
+        }
+    }
+    return true;
+}
+
 // Validates a metricActivation and populates state.
 // EventActivationMap and EventDeactivationMap are supplied to a MetricProducer
 //      to provide the producer with state about its activators and deactivators.
@@ -342,12 +377,32 @@
     return true;
 }
 
+bool initStates(const StatsdConfig& config, unordered_map<int64_t, int>& stateAtomIdMap,
+                unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps) {
+    for (int i = 0; i < config.state_size(); i++) {
+        const State& state = config.state(i);
+        const int64_t stateId = state.id();
+        stateAtomIdMap[stateId] = state.atom_id();
+
+        const StateMap& stateMap = state.map();
+        for (auto group : stateMap.group()) {
+            for (auto value : group.value()) {
+                allStateGroupMaps[stateId][value] = group.group_id();
+            }
+        }
+    }
+
+    return true;
+}
+
 bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs,
                  const int64_t currentTimeNs, UidMap& uidMap,
                  const sp<StatsPullerManager>& pullerManager,
                  const unordered_map<int64_t, int>& logTrackerMap,
                  const unordered_map<int64_t, int>& conditionTrackerMap,
                  const vector<sp<LogMatchingTracker>>& allAtomMatchers,
+                 const unordered_map<int64_t, int>& stateAtomIdMap,
+                 const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
                  vector<sp<ConditionTracker>>& allConditionTrackers,
                  vector<sp<MetricProducer>>& allMetricProducers,
                  unordered_map<int, vector<int>>& conditionToMetricMap,
@@ -398,10 +453,9 @@
 
         int conditionIndex = -1;
         if (metric.has_condition()) {
-            bool good = handleMetricWithConditions(
-                    metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
-                    allConditionTrackers, conditionIndex, conditionToMetricMap);
-            if (!good) {
+            if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+                                            metric.links(), allConditionTrackers, conditionIndex,
+                                            conditionToMetricMap)) {
                 return false;
             }
         } else {
@@ -411,6 +465,18 @@
             }
         }
 
+        std::vector<int> slicedStateAtoms;
+        unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+        if (metric.slice_by_state_size() > 0) {
+            if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+                                        allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+                return false;
+            }
+        }
+
+        // TODO(tsaichristine): add check for unequal number of MetricStateLinks
+        // and slice_by_states
+
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
         bool success = handleMetricActivation(config, metric.id(), metricIndex,
@@ -421,7 +487,7 @@
 
         sp<MetricProducer> countProducer = new CountMetricProducer(
                 key, metric, conditionIndex, wizard, timeBaseTimeNs, currentTimeNs,
-                eventActivationMap, eventDeactivationMap);
+                eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap);
         allMetricProducers.push_back(countProducer);
     }
 
@@ -721,6 +787,13 @@
     }
     for (const auto& it : allMetricProducers) {
         uidMap.addListener(it);
+
+        // Register metrics to StateTrackers
+        for (int atomId : it->getSlicedStateAtoms()) {
+            if (!StateManager::getInstance().registerListener(atomId, it)) {
+                return false;
+            }
+        }
     }
     return true;
 }
@@ -846,6 +919,8 @@
     unordered_map<int64_t, int> logTrackerMap;
     unordered_map<int64_t, int> conditionTrackerMap;
     unordered_map<int64_t, int> metricProducerMap;
+    unordered_map<int64_t, int> stateAtomIdMap;
+    unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
 
     if (!initLogTrackers(config, uidMap, logTrackerMap, allAtomMatchers, allTagIds)) {
         ALOGE("initLogMatchingTrackers failed");
@@ -858,9 +933,13 @@
         ALOGE("initConditionTrackers failed");
         return false;
     }
-
+    if (!initStates(config, stateAtomIdMap, allStateGroupMaps)) {
+        ALOGE("initStates failed");
+        return false;
+    }
     if (!initMetrics(key, config, timeBaseNs, currentTimeNs, uidMap, pullerManager, logTrackerMap,
-                     conditionTrackerMap, allAtomMatchers, allConditionTrackers, allMetricProducers,
+                     conditionTrackerMap, allAtomMatchers, stateAtomIdMap, allStateGroupMaps,
+                     allConditionTrackers, allMetricProducers,
                      conditionToMetricMap, trackerToMetricMap, metricProducerMap,
                      noReportMetricIds, activationAtomTrackerToMetricMap,
                      deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 3802948..95b2ab8 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -68,6 +68,17 @@
                     std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
                     std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks);
 
+// Initialize State maps using State protos in the config. These maps will
+// eventually be passed to MetricProducers to initialize their state info.
+// input:
+// [config]: the input config
+// output:
+// [stateAtomIdMap]: this map should contain the mapping from state ids to atom ids
+// [allStateGroupMaps]: this map should contain the mapping from states ids and state
+//                      values to state group ids for all states
+bool initStates(const StatsdConfig& config, unordered_map<int64_t, int>& stateAtomIdMap,
+                unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps);
+
 // Initialize MetricProducers.
 // input:
 // [key]: the config key that this config belongs to
@@ -75,6 +86,9 @@
 // [timeBaseSec]: start time base for all metrics
 // [logTrackerMap]: LogMatchingTracker name to index mapping from previous step.
 // [conditionTrackerMap]: condition name to index mapping
+// [stateAtomIdMap]: contains the mapping from state ids to atom ids
+// [allStateGroupMaps]: contains the mapping from atom ids and state values to
+//                      state group ids for all states
 // output:
 // [allMetricProducers]: contains the list of sp to the MetricProducers created.
 // [conditionToMetricMap]: contains the mapping from condition tracker index to
@@ -87,6 +101,8 @@
         const std::unordered_map<int64_t, int>& conditionTrackerMap,
         const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks,
         const vector<sp<LogMatchingTracker>>& allAtomMatchers,
+        const unordered_map<int64_t, int>& stateAtomIdMap,
+        const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
         vector<sp<ConditionTracker>>& allConditionTrackers,
         std::vector<sp<MetricProducer>>& allMetricProducers,
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
diff --git a/cmds/statsd/src/state/StateManager.cpp b/cmds/statsd/src/state/StateManager.cpp
index a3059c5..95b2c76 100644
--- a/cmds/statsd/src/state/StateManager.cpp
+++ b/cmds/statsd/src/state/StateManager.cpp
@@ -35,26 +35,25 @@
     }
 }
 
-bool StateManager::registerListener(int stateAtomId, wp<StateListener> listener) {
+bool StateManager::registerListener(int atomId, wp<StateListener> listener) {
     std::lock_guard<std::mutex> lock(mMutex);
 
     // Check if state tracker already exists
-    if (mStateTrackers.find(stateAtomId) == mStateTrackers.end()) {
+    if (mStateTrackers.find(atomId) == mStateTrackers.end()) {
         // Create a new state tracker iff atom is a state atom
-        auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(stateAtomId);
+        auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(atomId);
         if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) {
-            mStateTrackers[stateAtomId] = new StateTracker(stateAtomId, it->second);
+            mStateTrackers[atomId] = new StateTracker(atomId, it->second);
         } else {
-            ALOGE("StateManager cannot register listener, Atom %d is not a state atom",
-                  stateAtomId);
+            ALOGE("StateManager cannot register listener, Atom %d is not a state atom", atomId);
             return false;
         }
     }
-    mStateTrackers[stateAtomId]->registerListener(listener);
+    mStateTrackers[atomId]->registerListener(listener);
     return true;
 }
 
-void StateManager::unregisterListener(int stateAtomId, wp<StateListener> listener) {
+void StateManager::unregisterListener(int atomId, wp<StateListener> listener) {
     std::unique_lock<std::mutex> lock(mMutex);
 
     // Hold the sp<> until the lock is released so that ~StateTracker() is
@@ -62,7 +61,7 @@
     sp<StateTracker> toRemove;
 
     // Unregister listener from correct StateTracker
-    auto it = mStateTrackers.find(stateAtomId);
+    auto it = mStateTrackers.find(atomId);
     if (it != mStateTrackers.end()) {
         it->second->unregisterListener(listener);
 
@@ -73,15 +72,15 @@
         }
     } else {
         ALOGE("StateManager cannot unregister listener, StateTracker for atom %d does not exist",
-              stateAtomId);
+              atomId);
     }
     lock.unlock();
 }
 
-int StateManager::getState(int stateAtomId, const HashableDimensionKey& key) {
+int StateManager::getStateValue(int atomId, const HashableDimensionKey& key) {
     std::lock_guard<std::mutex> lock(mMutex);
-    if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
-        return mStateTrackers[stateAtomId]->getState(key);
+    if (mStateTrackers.find(atomId) != mStateTrackers.end()) {
+        return mStateTrackers[atomId]->getStateValue(key);
     }
 
     return StateTracker::kStateUnknown;
diff --git a/cmds/statsd/src/state/StateManager.h b/cmds/statsd/src/state/StateManager.h
index ce60f14..89ee6c0 100644
--- a/cmds/statsd/src/state/StateManager.h
+++ b/cmds/statsd/src/state/StateManager.h
@@ -15,10 +15,11 @@
  */
 #pragma once
 
-//#include <utils/Log.h>
+#include <gtest/gtest_prod.h>
+#include <inttypes.h>
 #include <utils/RefBase.h>
-#include "HashableDimensionKey.h"
 
+#include "HashableDimensionKey.h"
 #include "state/StateListener.h"
 #include "state/StateTracker.h"
 
@@ -38,29 +39,29 @@
     // Notifies the correct StateTracker of an event.
     void onLogEvent(const LogEvent& event);
 
-    // Returns true if stateAtomId is the id of a state atom and notifies the
-    // correct StateTracker to register the listener. If the correct
-    // StateTracker does not exist, a new StateTracker is created.
-    bool registerListener(int stateAtomId, wp<StateListener> listener);
+    // Returns true if atomId is being tracked and is associated with a state
+    // atom. StateManager notifies the correct StateTracker to register listener.
+    // If the correct StateTracker does not exist, a new StateTracker is created.
+    bool registerListener(int atomId, wp<StateListener> listener);
 
     // Notifies the correct StateTracker to unregister a listener
     // and removes the tracker if it no longer has any listeners.
-    void unregisterListener(int stateAtomId, wp<StateListener> listener);
+    void unregisterListener(int atomId, wp<StateListener> listener);
 
-    // Queries the correct StateTracker for the state that is mapped to the given
-    // query key.
+    // Queries the correct StateTracker for the original/un-mapped state value
+    // that is mapped to the given query key.
     // If the StateTracker doesn't exist, returns StateTracker::kStateUnknown.
-    int getState(int stateAtomId, const HashableDimensionKey& queryKey);
+    int getStateValue(int atomId, const HashableDimensionKey& queryKey);
 
     inline int getStateTrackersCount() {
         std::lock_guard<std::mutex> lock(mMutex);
         return mStateTrackers.size();
     }
 
-    inline int getListenersCount(int stateAtomId) {
+    inline int getListenersCount(int atomId) {
         std::lock_guard<std::mutex> lock(mMutex);
-        if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
-            return mStateTrackers[stateAtomId]->getListenersCount();
+        if (mStateTrackers.find(atomId) != mStateTrackers.end()) {
+            return mStateTrackers[atomId]->getListenersCount();
         }
         return -1;
     }
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
index 5a91950..323fc0e 100644
--- a/cmds/statsd/src/state/StateTracker.cpp
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -30,13 +30,13 @@
   : mAtomId(atomId),
     mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) {
     // create matcher for each primary field
-    // TODO(tsaichristine): handle when primary field is first uid in chain
+    // TODO(tsaichristine): b/142108433 handle when primary field is first uid in chain
     for (const auto& primary : stateAtomInfo.primaryFields) {
         Matcher matcher = getSimpleMatcher(atomId, primary);
         mPrimaryFields.push_back(matcher);
     }
 
-    // TODO(tsaichristine): set default state, reset state, and nesting
+    // TODO(tsaichristine): b/142108433 set default state, reset state, and nesting
 }
 
 void StateTracker::onLogEvent(const LogEvent& event) {
@@ -96,7 +96,7 @@
     mListeners.erase(listener);
 }
 
-int StateTracker::getState(const HashableDimensionKey& queryKey) const {
+int StateTracker::getStateValue(const HashableDimensionKey& queryKey) const {
     if (queryKey.getValues().size() == mPrimaryFields.size()) {
         auto it = mStateMap.find(queryKey);
         if (it != mStateMap.end()) {
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
index f22706c..cfa9fd8 100644
--- a/cmds/statsd/src/state/StateTracker.h
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -48,7 +48,7 @@
     // Returns the state value mapped to the given query key.
     // If the key isn't mapped to a state or the key size doesn't match the
     // primary key size, the default state is returned.
-    int getState(const HashableDimensionKey& queryKey) const;
+    int getStateValue(const HashableDimensionKey& queryKey) const;
 
     inline int getListenersCount() const {
         return mListeners.size();
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index 67625eb..c22e3cc 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -445,6 +445,8 @@
             return 12 * 60 * 60 * 1000LL;
         case ONE_DAY:
             return 24 * 60 * 60 * 1000LL;
+        case ONE_WEEK:
+            return 7 * 24 * 60 * 60 * 1000LL;
         case CTS:
             return 1000;
         case TIME_UNIT_UNSPECIFIED:
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index c107397..c98b2cf 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -44,6 +44,7 @@
   SIX_HOURS = 7;
   TWELVE_HOURS = 8;
   ONE_DAY = 9;
+  ONE_WEEK = 10;
   CTS = 1000;
 }
 
diff --git a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
new file mode 100644
index 0000000..6591d69
--- /dev/null
+++ b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
@@ -0,0 +1,197 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "src/StatsLogProcessor.h"
+#include "src/state/StateManager.h"
+#include "tests/statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+TEST(CountMetricE2eTest, TestWithSimpleState) {
+    // Initialize config
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    auto syncStartMatcher = CreateSyncStartAtomMatcher();
+    *config.add_atom_matcher() = syncStartMatcher;
+
+    auto state = CreateScreenState();
+    *config.add_state() = state;
+
+    // Create count metric that slices by screen state
+    int64_t metricId = 123456;
+    auto countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(syncStartMatcher.id());
+    countMetric->set_bucket(TimeUnit::ONE_MINUTE);
+    countMetric->add_slice_by_state(state.id());
+
+    // Initialize StatsLogProcessor
+    const int64_t baseTimeNs = 0;                                   // 0:00
+    const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC;  // 0:01
+    const int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
+
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey);
+
+    // Check that StateTrackers were properly initialized.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1,
+              StateManager::getInstance().getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Check that CountMetricProducer was initialized correctly.
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), android::util::SCREEN_STATE_CHANGED);
+    EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+}
+
+TEST(CountMetricE2eTest, TestWithMappedState) {
+    // Initialize config
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    auto syncStartMatcher = CreateSyncStartAtomMatcher();
+    *config.add_atom_matcher() = syncStartMatcher;
+
+    auto state = CreateScreenStateWithOnOffMap();
+    *config.add_state() = state;
+
+    // Create count metric that slices by screen state with on/off map
+    int64_t metricId = 123456;
+    auto countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(syncStartMatcher.id());
+    countMetric->set_bucket(TimeUnit::ONE_MINUTE);
+    countMetric->add_slice_by_state(state.id());
+
+    // Initialize StatsLogProcessor
+    const int64_t baseTimeNs = 0;                                   // 0:00
+    const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC;  // 0:01
+    const int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
+
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey);
+
+    // Check that StateTrackers were properly initialized.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1,
+              StateManager::getInstance().getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Check that CountMetricProducer was initialized correctly.
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), android::util::SCREEN_STATE_CHANGED);
+    EXPECT_EQ(metricProducer->mStateGroupMap.size(), 1);
+
+    StateMap map = state.map();
+    for (auto group : map.group()) {
+        for (auto value : group.value()) {
+            EXPECT_EQ(metricProducer->mStateGroupMap[android::util::SCREEN_STATE_CHANGED][value],
+                      group.group_id());
+        }
+    }
+}
+
+TEST(CountMetricE2eTest, TestWithMultipleStates) {
+    // Initialize config
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    auto syncStartMatcher = CreateSyncStartAtomMatcher();
+    *config.add_atom_matcher() = syncStartMatcher;
+
+    auto state1 = CreateScreenStateWithOnOffMap();
+    *config.add_state() = state1;
+    auto state2 = CreateUidProcessState();
+    *config.add_state() = state2;
+
+    // Create count metric that slices by screen state with on/off map
+    int64_t metricId = 123456;
+    auto countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(syncStartMatcher.id());
+    countMetric->set_bucket(TimeUnit::ONE_MINUTE);
+    countMetric->add_slice_by_state(state1.id());
+    countMetric->add_slice_by_state(state2.id());
+
+    // Initialize StatsLogProcessor
+    const int64_t baseTimeNs = 0;                                   // 0:00
+    const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC;  // 0:01
+    const int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
+
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey);
+
+    // Check that StateTrackers were properly initialized.
+    EXPECT_EQ(2, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1,
+              StateManager::getInstance().getListenersCount(android::util::SCREEN_STATE_CHANGED));
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(
+                         android::util::UID_PROCESS_STATE_CHANGED));
+
+    // Check that CountMetricProducer was initialized correctly.
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 2);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), android::util::SCREEN_STATE_CHANGED);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(1), android::util::UID_PROCESS_STATE_CHANGED);
+    EXPECT_EQ(metricProducer->mStateGroupMap.size(), 1);
+
+    StateMap map = state1.map();
+    for (auto group : map.group()) {
+        for (auto value : group.value()) {
+            EXPECT_EQ(metricProducer->mStateGroupMap[android::util::SCREEN_STATE_CHANGED][value],
+                      group.group_id());
+        }
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 839daa4..8915c73 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -43,8 +43,8 @@
     metric.set_bucket(ONE_MINUTE);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-                                      5, 600 * NS_PER_SEC + NS_PER_SEC/2);
+    CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, 5,
+                                      600 * NS_PER_SEC + NS_PER_SEC / 2);
     EXPECT_EQ(600500000000, countProducer.mCurrentBucketStartTimeNs);
     EXPECT_EQ(10, countProducer.mCurrentBucketNum);
     EXPECT_EQ(660000000005, countProducer.getCurrentBucketEndTimeNs());
@@ -131,7 +131,8 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
-    CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs, bucketStartTimeNs);
+    CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs,
+                                      bucketStartTimeNs);
 
     countProducer.onConditionChanged(true, bucketStartTimeNs);
     countProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
@@ -396,6 +397,26 @@
             std::ceil(1.0 * event7.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
 }
 
+TEST(CountMetricProducerTest, TestOneWeekTimeUnit) {
+    CountMetric metric;
+    metric.set_id(1);
+    metric.set_bucket(ONE_WEEK);
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    int64_t oneDayNs = 24 * 60 * 60 * 1e9;
+    int64_t fiveWeeksNs = 5 * 7 * oneDayNs;
+
+    CountMetricProducer countProducer(
+            kConfigKey, metric, -1 /* meaning no condition */, wizard, oneDayNs, fiveWeeksNs);
+
+    int64_t fiveWeeksOneDayNs = fiveWeeksNs + oneDayNs;
+
+    EXPECT_EQ(fiveWeeksNs, countProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(4, countProducer.mCurrentBucketNum);
+    EXPECT_EQ(fiveWeeksOneDayNs, countProducer.getCurrentBucketEndTimeNs());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index c89ffea..8d38000 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -44,7 +44,7 @@
 
     std::vector<Update> updates;
 
-    void onStateChanged(int stateAtomId, const HashableDimensionKey& primaryKey, int oldState,
+    void onStateChanged(int atomId, const HashableDimensionKey& primaryKey, int oldState,
                         int newState) {
         updates.emplace_back(primaryKey, newState);
     }
@@ -82,6 +82,7 @@
     return event;
 }
 
+// Incorrect event - missing fields
 std::shared_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName, int state) {
     std::shared_ptr<LogEvent> event =
             std::make_shared<LogEvent>(android::util::OVERLAY_STATE_CHANGED, 1000 /*timestamp*/);
@@ -91,6 +92,18 @@
     event->init();
     return event;
 }
+
+// Incorrect event - exclusive state has wrong type
+std::shared_ptr<LogEvent> buildOverlayEventBadStateType(int uid, const std::string& packageName) {
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::OVERLAY_STATE_CHANGED, 1000 /*timestamp*/);
+    event->write((int32_t)uid);
+    event->write(packageName);
+    event->write(true);
+    event->write("string");  // exclusive state: string instead of int
+    event->init();
+    return event;
+}
 // END: build event functions.
 
 // START: get primary key functions
@@ -148,22 +161,22 @@
 
     // Register listener to non-existing StateTracker
     EXPECT_EQ(0, mgr.getStateTrackersCount());
-    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_TRUE(mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1));
     EXPECT_EQ(1, mgr.getStateTrackersCount());
     EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
 
     // Register listener to existing StateTracker
-    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_TRUE(mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2));
     EXPECT_EQ(1, mgr.getStateTrackersCount());
     EXPECT_EQ(2, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
 
     // Register already registered listener to existing StateTracker
-    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_TRUE(mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2));
     EXPECT_EQ(1, mgr.getStateTrackersCount());
     EXPECT_EQ(2, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
 
     // Register listener to non-state atom
-    mgr.registerListener(android::util::BATTERY_LEVEL_CHANGED, listener2);
+    EXPECT_FALSE(mgr.registerListener(android::util::BATTERY_LEVEL_CHANGED, listener2));
     EXPECT_EQ(1, mgr.getStateTrackersCount());
 }
 
@@ -227,12 +240,12 @@
 
     // check StateTracker was updated by querying for state
     HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY;
-    EXPECT_EQ(2, mgr.getState(android::util::SCREEN_STATE_CHANGED, queryKey));
+    EXPECT_EQ(2, mgr.getStateValue(android::util::SCREEN_STATE_CHANGED, queryKey));
 }
 
 /**
  * Test StateManager's onLogEvent and StateListener's onStateChanged correctly
- * updates listener for states with primary keys.
+ * updates listener for states with one primary key.
  */
 TEST(StateTrackerTest, TestStateChangeOnePrimaryField) {
     sp<TestStateListener> listener1 = new TestStateListener();
@@ -240,9 +253,8 @@
     mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener1);
 
     // log event
-    std::shared_ptr<LogEvent> event = buildUidProcessEvent(
-            1000,
-            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  //  state value: 1002
+    std::shared_ptr<LogEvent> event =
+            buildUidProcessEvent(1000 /* uid */, android::app::ProcessStateEnum::PROCESS_STATE_TOP);
     mgr.onLogEvent(*event);
 
     // check listener was updated
@@ -252,23 +264,33 @@
 
     // check StateTracker was updated by querying for state
     HashableDimensionKey queryKey;
-    getUidProcessKey(1000, &queryKey);
-    EXPECT_EQ(1002, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey));
+    getUidProcessKey(1000 /* uid */, &queryKey);
+    EXPECT_EQ(1002, mgr.getStateValue(android::util::UID_PROCESS_STATE_CHANGED, queryKey));
 }
 
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged correctly
+ * updates listener for states with multiple primary keys.
+ */
 TEST(StateTrackerTest, TestStateChangeMultiplePrimaryFields) {
     sp<TestStateListener> listener1 = new TestStateListener();
     StateManager mgr;
     mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener1);
 
     // log event
-    std::shared_ptr<LogEvent> event = buildOverlayEvent(1000, "package1", 1);  // state: ENTERED
+    std::shared_ptr<LogEvent> event =
+            buildOverlayEvent(1000 /* uid */, "package1", 1);  // state: ENTERED
     mgr.onLogEvent(*event);
 
-    // check listener update
+    // check listener was updated
     EXPECT_EQ(1, listener1->updates.size());
     EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
     EXPECT_EQ(1, listener1->updates[0].mState);
+
+    // check StateTracker was updated by querying for state
+    HashableDimensionKey queryKey;
+    getOverlayKey(1000 /* uid */, "package1", &queryKey);
+    EXPECT_EQ(1, mgr.getStateValue(android::util::OVERLAY_STATE_CHANGED, queryKey));
 }
 
 /**
@@ -282,11 +304,14 @@
     mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener1);
 
     // log event
-    std::shared_ptr<LogEvent> event =
-            buildIncorrectOverlayEvent(1000, "package1", 1);  // state: ENTERED
-    mgr.onLogEvent(*event);
+    std::shared_ptr<LogEvent> event1 =
+            buildIncorrectOverlayEvent(1000 /* uid */, "package1", 1 /* state */);
+    std::shared_ptr<LogEvent> event2 = buildOverlayEventBadStateType(1001 /* uid */, "package2");
 
-    // check listener update
+    // check listener was updated
+    mgr.onLogEvent(*event1);
+    EXPECT_EQ(0, listener1->updates.size());
+    mgr.onLogEvent(*event2);
     EXPECT_EQ(0, listener1->updates.size());
 }
 
@@ -301,18 +326,19 @@
 
     std::shared_ptr<LogEvent> event1 = buildUidProcessEvent(
             1000,
-            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  // state value: 1002
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  //  state value: 1002
     std::shared_ptr<LogEvent> event2 = buildUidProcessEvent(
             1001,
-            android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE);  // state value: 1003
+            android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE);  //  state value:
+                                                                                //  1003
     std::shared_ptr<LogEvent> event3 = buildUidProcessEvent(
             1002,
-            android::app::ProcessStateEnum::PROCESS_STATE_PERSISTENT);  // state value: 1000
+            android::app::ProcessStateEnum::PROCESS_STATE_PERSISTENT);  //  state value: 1000
     std::shared_ptr<LogEvent> event4 = buildUidProcessEvent(
             1001,
-            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  // state value: 1002
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  //  state value: 1002
     std::shared_ptr<LogEvent> event5 =
-            buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON);  // state value:
+            buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
     std::shared_ptr<LogEvent> event6 = buildOverlayEvent(1000, "package1", 1);
     std::shared_ptr<LogEvent> event7 = buildOverlayEvent(1000, "package2", 2);
 
@@ -327,25 +353,25 @@
     // Query for UidProcessState of uid 1001
     HashableDimensionKey queryKey1;
     getUidProcessKey(1001, &queryKey1);
-    EXPECT_EQ(1003, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+    EXPECT_EQ(1003, mgr.getStateValue(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
 
     // Query for UidProcessState of uid 1004 - not in state map
     HashableDimensionKey queryKey2;
     getUidProcessKey(1004, &queryKey2);
-    EXPECT_EQ(-1,
-              mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey2));  // default state
+    EXPECT_EQ(-1, mgr.getStateValue(android::util::UID_PROCESS_STATE_CHANGED,
+                                    queryKey2));  // default state
 
     // Query for UidProcessState of uid 1001 - after change in state
     mgr.onLogEvent(*event4);
-    EXPECT_EQ(1002, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+    EXPECT_EQ(1002, mgr.getStateValue(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
 
     // Query for ScreenState
-    EXPECT_EQ(2, mgr.getState(android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
+    EXPECT_EQ(2, mgr.getStateValue(android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
 
     // Query for OverlayState of uid 1000, package name "package2"
     HashableDimensionKey queryKey3;
     getOverlayKey(1000, "package2", &queryKey3);
-    EXPECT_EQ(2, mgr.getState(android::util::OVERLAY_STATE_CHANGED, queryKey3));
+    EXPECT_EQ(2, mgr.getStateValue(android::util::OVERLAY_STATE_CHANGED, queryKey3));
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 2c4f3c7..38c22ab 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -251,6 +251,101 @@
     return predicate;
 }
 
+State CreateScreenState() {
+    State state;
+    state.set_id(StringToId("ScreenState"));
+    state.set_atom_id(29);
+    return state;
+}
+
+State CreateUidProcessState() {
+    State state;
+    state.set_id(StringToId("UidProcessState"));
+    state.set_atom_id(27);
+    return state;
+}
+
+State CreateOverlayState() {
+    State state;
+    state.set_id(StringToId("OverlayState"));
+    state.set_atom_id(59);
+    return state;
+}
+
+State CreateScreenStateWithOnOffMap() {
+    State state;
+    state.set_id(StringToId("ScreenStateOnOff"));
+    state.set_atom_id(29);
+
+    auto map = CreateScreenStateOnOffMap();
+    *state.mutable_map() = map;
+
+    return state;
+}
+
+State CreateScreenStateWithInDozeMap() {
+    State state;
+    state.set_id(StringToId("ScreenStateInDoze"));
+    state.set_atom_id(29);
+
+    auto map = CreateScreenStateInDozeMap();
+    *state.mutable_map() = map;
+
+    return state;
+}
+
+StateMap_StateGroup CreateScreenStateOnGroup() {
+    StateMap_StateGroup group;
+    group.set_group_id(StringToId("SCREEN_ON"));
+    group.add_value(2);
+    group.add_value(5);
+    group.add_value(6);
+    return group;
+}
+
+StateMap_StateGroup CreateScreenStateOffGroup() {
+    StateMap_StateGroup group;
+    group.set_group_id(StringToId("SCREEN_OFF"));
+    group.add_value(0);
+    group.add_value(1);
+    group.add_value(3);
+    group.add_value(4);
+    return group;
+}
+
+StateMap CreateScreenStateOnOffMap() {
+    StateMap map;
+    *map.add_group() = CreateScreenStateOnGroup();
+    *map.add_group() = CreateScreenStateOffGroup();
+    return map;
+}
+
+StateMap_StateGroup CreateScreenStateInDozeGroup() {
+    StateMap_StateGroup group;
+    group.set_group_id(StringToId("SCREEN_DOZE"));
+    group.add_value(3);
+    group.add_value(4);
+    return group;
+}
+
+StateMap_StateGroup CreateScreenStateNotDozeGroup() {
+    StateMap_StateGroup group;
+    group.set_group_id(StringToId("SCREEN_NOT_DOZE"));
+    group.add_value(0);
+    group.add_value(1);
+    group.add_value(2);
+    group.add_value(5);
+    group.add_value(6);
+    return group;
+}
+
+StateMap CreateScreenStateInDozeMap() {
+    StateMap map;
+    *map.add_group() = CreateScreenStateInDozeGroup();
+    *map.add_group() = CreateScreenStateNotDozeGroup();
+    return map;
+}
+
 void addPredicateToPredicateCombination(const Predicate& predicate,
                                         Predicate* combinationPredicate) {
     combinationPredicate->mutable_combination()->add_predicate(predicate.id());
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 635c583..c026105 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -104,6 +104,37 @@
 // Create a Predicate proto for app is in background.
 Predicate CreateIsInBackgroundPredicate();
 
+// Create State proto for screen state atom.
+State CreateScreenState();
+
+// Create State proto for uid process state atom.
+State CreateUidProcessState();
+
+// Create State proto for overlay state atom.
+State CreateOverlayState();
+
+State CreateScreenStateWithOnOffMap();
+
+State CreateScreenStateWithInDozeMap();
+
+// Create StateGroup proto for ScreenState ON group
+StateMap_StateGroup CreateScreenStateOnGroup();
+
+// Create StateGroup proto for ScreenState OFF group
+StateMap_StateGroup CreateScreenStateOffGroup();
+
+// Create StateMap proto for ScreenState ON/OFF map
+StateMap CreateScreenStateOnOffMap();
+
+// Create StateGroup proto for ScreenState IN DOZE group
+StateMap_StateGroup CreateScreenStateInDozeGroup();
+
+// Create StateGroup proto for ScreenState NOT IN DOZE group
+StateMap_StateGroup CreateScreenStateNotDozeGroup();
+
+// Create StateMap proto for ScreenState IN DOZE map
+StateMap CreateScreenStateInDozeMap();
+
 // Add a predicate to the predicate combination.
 void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination);
 
@@ -319,4 +350,4 @@
 }
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 86bf20a..03ef286 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -3160,6 +3160,15 @@
     }
 
     @Override
+    public String[] getTelephonyPackageNames() {
+        try {
+            return mPM.getTelephonyPackageNames();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
     public String getSystemCaptionsServicePackageName() {
         try {
             return mPM.getSystemCaptionsServicePackageName();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 39fab63..d5bc9b0 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2212,9 +2212,9 @@
     }
 
     @Override
-    public Context createContextAsUser(UserHandle user) {
+    public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) {
         try {
-            return createPackageContextAsUser(getPackageName(), mFlags, user);
+            return createPackageContextAsUser(getPackageName(), flags, user);
         } catch (NameNotFoundException e) {
             throw new IllegalStateException("Own package not found: package=" + getPackageName());
         }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index dda3bb5..f591441 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -215,7 +215,6 @@
 
     void releaseSomeActivities(in IApplicationThread app);
     Bitmap getTaskDescriptionIcon(in String filename, int userId);
-    void startInPlaceAnimationOnFrontMostApplication(in Bundle opts);
     void registerTaskStackListener(in ITaskStackListener listener);
     void unregisterTaskStackListener(in ITaskStackListener listener);
     void setTaskResizeable(int taskId, int resizeableMode);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 316cab8..58f6741 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -997,8 +997,13 @@
     }
 
     /**
-     * @hide
+     * <p>
+     *  Gets the currently applied notification policy. If {@link #getCurrentInterruptionFilter}
+     * is equal to {@link #INTERRUPTION_FILTER_ALL}, then the consolidated notification policy
+     * will match the default notification policy returned by {@link #getNotificationPolicy}.
+     * </p>
      */
+    @Nullable
     public NotificationManager.Policy getConsolidatedNotificationPolicy() {
         INotificationManager service = getService();
         try {
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index cb9ebac..68ab89c 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -32,6 +32,7 @@
 import android.content.res.Resources;
 import android.content.res.ResourcesImpl;
 import android.content.res.ResourcesKey;
+import android.content.res.loader.ResourceLoader;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.IBinder;
 import android.os.Process;
@@ -45,6 +46,7 @@
 import android.view.Display;
 import android.view.DisplayAdjustments;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
@@ -53,7 +55,11 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.WeakHashMap;
 import java.util.function.Predicate;
@@ -92,6 +98,52 @@
             new ArrayMap<>();
 
     /**
+     * A list of {@link Resources} that contain unique {@link ResourcesImpl}s.
+     *
+     * These are isolated so that {@link ResourceLoader}s can be added and removed without
+     * affecting other instances.
+     *
+     * When a reference is added here, it is guaranteed that the {@link ResourcesImpl}
+     * it contains is unique to itself and will never be set to a shared reference.
+     */
+    @GuardedBy("this")
+    private List<ResourcesWithLoaders> mResourcesWithLoaders = Collections.emptyList();
+
+    private static class ResourcesWithLoaders {
+
+        private WeakReference<Resources> mResources;
+        private ResourcesKey mResourcesKey;
+
+        @Nullable
+        private WeakReference<IBinder> mActivityToken;
+
+        ResourcesWithLoaders(Resources resources, ResourcesKey resourcesKey,
+                IBinder activityToken) {
+            this.mResources = new WeakReference<>(resources);
+            this.mResourcesKey = resourcesKey;
+            this.mActivityToken = new WeakReference<>(activityToken);
+        }
+
+        @Nullable
+        Resources resources() {
+            return mResources.get();
+        }
+
+        @Nullable
+        IBinder activityToken() {
+            return mActivityToken == null ? null : mActivityToken.get();
+        }
+
+        ResourcesKey resourcesKey() {
+            return mResourcesKey;
+        }
+
+        void updateKey(ResourcesKey newKey) {
+            mResourcesKey = newKey;
+        }
+    }
+
+    /**
      * A list of Resource references that can be reused.
      */
     @UnsupportedAppUsage
@@ -182,15 +234,36 @@
     public void invalidatePath(String path) {
         synchronized (this) {
             int count = 0;
-            for (int i = 0; i < mResourceImpls.size();) {
+
+            for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
                 final ResourcesKey key = mResourceImpls.keyAt(i);
                 if (key.isPathReferenced(path)) {
-                    cleanupResourceImpl(key);
+                    ResourcesImpl impl = mResourceImpls.removeAt(i).get();
+                    if (impl != null) {
+                        impl.flushLayoutCache();
+                    }
                     count++;
-                } else {
-                    i++;
                 }
             }
+
+            for (int i = mResourcesWithLoaders.size() - 1; i >= 0; i--) {
+                ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(i);
+                Resources resources = resourcesWithLoaders.resources();
+                if (resources == null) {
+                    continue;
+                }
+
+                final ResourcesKey key = resourcesWithLoaders.resourcesKey();
+                if (key.isPathReferenced(path)) {
+                    mResourcesWithLoaders.remove(i);
+                    ResourcesImpl impl = resources.getImpl();
+                    if (impl != null) {
+                        impl.flushLayoutCache();
+                    }
+                    count++;
+                }
+            }
+
             Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
 
             for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
@@ -317,15 +390,6 @@
         }
     }
 
-    private void cleanupResourceImpl(ResourcesKey removedKey) {
-        // Remove resource key to resource impl mapping and flush cache
-        final ResourcesImpl res = mResourceImpls.remove(removedKey).get();
-
-        if (res != null) {
-            res.flushLayoutCache();
-        }
-    }
-
     private static String overlayPathToIdmapPath(String path) {
         return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
     }
@@ -499,6 +563,16 @@
 
             pw.print("resource impls: ");
             pw.println(countLiveReferences(mResourceImpls.values()));
+
+            int resourcesWithLoadersCount = 0;
+            for (int index = 0; index < mResourcesWithLoaders.size(); index++) {
+                if (mResourcesWithLoaders.get(index).resources() != null) {
+                    resourcesWithLoadersCount++;
+                }
+            }
+
+            pw.print("resources with loaders: ");
+            pw.println(resourcesWithLoadersCount);
         }
     }
 
@@ -579,11 +653,24 @@
      */
     private @Nullable ResourcesKey findKeyForResourceImplLocked(
             @NonNull ResourcesImpl resourceImpl) {
-        final int refCount = mResourceImpls.size();
+        int size = mResourcesWithLoaders.size();
+        for (int index = 0; index < size; index++) {
+            ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+            Resources resources = resourcesWithLoaders.resources();
+            if (resources == null) {
+                continue;
+            }
+
+            if (resourceImpl == resources.getImpl()) {
+                return resourcesWithLoaders.resourcesKey();
+            }
+        }
+
+        int refCount = mResourceImpls.size();
         for (int i = 0; i < refCount; i++) {
             WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
             ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
-            if (impl != null && resourceImpl == impl) {
+            if (resourceImpl == impl) {
                 return mResourceImpls.keyAt(i);
             }
         }
@@ -625,31 +712,55 @@
         return activityResources;
     }
 
-    /**
-     * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
-     * or the class loader is different.
-     */
-    private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
+    @Nullable
+    private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken,
+            @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) {
+        int size = mResourcesWithLoaders.size();
+        for (int index = 0; index < size; index++) {
+            ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+            Resources resources = resourcesWithLoaders.resources();
+            if (resources == null) {
+                continue;
+            }
+
+            IBinder activityToken = resourcesWithLoaders.activityToken();
+            ResourcesKey key = resourcesWithLoaders.resourcesKey();
+
+            ClassLoader classLoader = resources.getClassLoader();
+
+            if (Objects.equals(activityToken, targetActivityToken)
+                    && Objects.equals(key, targetKey)
+                    && Objects.equals(classLoader, targetClassLoader)) {
+                return resources;
+            }
+        }
+
+        ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
+                targetActivityToken);
+
+        size = activityResources.activityResources.size();
+        for (int index = 0; index < size; index++) {
+            WeakReference<Resources> ref = activityResources.activityResources.get(index);
+            Resources resources = ref.get();
+            ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked(
+                    resources.getImpl());
+
+            if (key != null
+                    && Objects.equals(resources.getClassLoader(), targetClassLoader)
+                    && Objects.equals(key, targetKey)) {
+                return resources;
+            }
+        }
+
+        return null;
+    }
+
+    private @NonNull Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
             @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
             @NonNull CompatibilityInfo compatInfo) {
         final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
                 activityToken);
 
-        final int refCount = activityResources.activityResources.size();
-        for (int i = 0; i < refCount; i++) {
-            WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
-            Resources resources = weakResourceRef.get();
-
-            if (resources != null
-                    && Objects.equals(resources.getClassLoader(), classLoader)
-                    && resources.getImpl() == impl) {
-                if (DEBUG) {
-                    Slog.d(TAG, "- using existing ref=" + resources);
-                }
-                return resources;
-            }
-        }
-
         Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                 : new Resources(classLoader);
         resources.setImpl(impl);
@@ -661,28 +772,8 @@
         return resources;
     }
 
-    /**
-     * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
-     * otherwise creates a new Resources object.
-     */
-    private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
+    private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
             @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
-        // Find an existing Resources that has this ResourcesImpl set.
-        final int refCount = mResourceReferences.size();
-        for (int i = 0; i < refCount; i++) {
-            WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
-            Resources resources = weakResourceRef.get();
-            if (resources != null &&
-                    Objects.equals(resources.getClassLoader(), classLoader) &&
-                    resources.getImpl() == impl) {
-                if (DEBUG) {
-                    Slog.d(TAG, "- using existing ref=" + resources);
-                }
-                return resources;
-            }
-        }
-
-        // Create a new Resources reference and use the existing ResourcesImpl object.
         Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                 : new Resources(classLoader);
         resources.setImpl(impl);
@@ -750,16 +841,73 @@
             updateResourcesForActivity(activityToken, overrideConfig, displayId,
                     false /* movedToDifferentDisplay */);
 
+            cleanupReferences(activityToken);
+            rebaseKeyForActivity(activityToken, key);
+
+            synchronized (this) {
+                Resources resources = findResourcesForActivityLocked(activityToken, key,
+                        classLoader);
+                if (resources != null) {
+                    return resources;
+                }
+            }
+
             // Now request an actual Resources object.
-            return getOrCreateResources(activityToken, key, classLoader);
+            return createResources(activityToken, key, classLoader);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
         }
     }
 
     /**
-     * Gets an existing Resources object set with a ResourcesImpl object matching the given key,
-     * or creates one if it doesn't exist.
+     * Rebases a key's override config on top of the Activity's base override.
+     */
+    private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key) {
+        final ActivityResources activityResources =
+                getOrCreateActivityResourcesStructLocked(activityToken);
+
+        // Clean up any dead references so they don't pile up.
+        ArrayUtils.unstableRemoveIf(activityResources.activityResources,
+                sEmptyReferencePredicate);
+
+        // Rebase the key's override config on top of the Activity's base override.
+        if (key.hasOverrideConfiguration()
+                && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
+            final Configuration temp = new Configuration(activityResources.overrideConfig);
+            temp.updateFrom(key.mOverrideConfiguration);
+            key.mOverrideConfiguration.setTo(temp);
+        }
+    }
+
+    /**
+     * Check WeakReferences and remove any dead references so they don't pile up.
+     * @param activityToken optional token to clean up Activity resources
+     */
+    private void cleanupReferences(IBinder activityToken) {
+        synchronized (this) {
+            if (activityToken != null) {
+                ActivityResources activityResources = mActivityResourceReferences.get(
+                        activityToken);
+                if (activityResources != null) {
+                    ArrayUtils.unstableRemoveIf(activityResources.activityResources,
+                            sEmptyReferencePredicate);
+                }
+            } else {
+                ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
+            }
+
+            for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) {
+                ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+                Resources resources = resourcesWithLoaders.resources();
+                if (resources == null) {
+                    mResourcesWithLoaders.remove(index);
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates a Resources object set with a ResourcesImpl object matching the given key.
      *
      * @param activityToken The Activity this Resources object should be associated with.
      * @param key The key describing the parameters of the ResourcesImpl object.
@@ -769,7 +917,7 @@
      *         {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
      *         is called.
      */
-    private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
+    private @Nullable Resources createResources(@Nullable IBinder activityToken,
             @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
         synchronized (this) {
             if (DEBUG) {
@@ -778,66 +926,17 @@
                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
             }
 
-            if (activityToken != null) {
-                final ActivityResources activityResources =
-                        getOrCreateActivityResourcesStructLocked(activityToken);
-
-                // Clean up any dead references so they don't pile up.
-                ArrayUtils.unstableRemoveIf(activityResources.activityResources,
-                        sEmptyReferencePredicate);
-
-                // Rebase the key's override config on top of the Activity's base override.
-                if (key.hasOverrideConfiguration()
-                        && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
-                    final Configuration temp = new Configuration(activityResources.overrideConfig);
-                    temp.updateFrom(key.mOverrideConfiguration);
-                    key.mOverrideConfiguration.setTo(temp);
-                }
-
-                ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
-                if (resourcesImpl != null) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "- using existing impl=" + resourcesImpl);
-                    }
-                    return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
-                            resourcesImpl, key.mCompatInfo);
-                }
-
-                // We will create the ResourcesImpl object outside of holding this lock.
-
-            } else {
-                // Clean up any dead references so they don't pile up.
-                ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
-
-                // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
-                ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
-                if (resourcesImpl != null) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "- using existing impl=" + resourcesImpl);
-                    }
-                    return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
-                }
-
-                // We will create the ResourcesImpl object outside of holding this lock.
-            }
-
-            // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
-            ResourcesImpl resourcesImpl = createResourcesImpl(key);
+            ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key);
             if (resourcesImpl == null) {
                 return null;
             }
 
-            // Add this ResourcesImpl to the cache.
-            mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
-
-            final Resources resources;
             if (activityToken != null) {
-                resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
+                return createResourcesForActivityLocked(activityToken, classLoader,
                         resourcesImpl, key.mCompatInfo);
             } else {
-                resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
+                return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
             }
-            return resources;
         }
     }
 
@@ -868,7 +967,8 @@
      * {@link ClassLoader#getSystemClassLoader()} is used.
      * @return a Resources object from which to access resources.
      */
-    public @Nullable Resources getResources(@Nullable IBinder activityToken,
+    public @Nullable Resources getResources(
+            @Nullable IBinder activityToken,
             @Nullable String resDir,
             @Nullable String[] splitResDirs,
             @Nullable String[] overlayDirs,
@@ -888,7 +988,14 @@
                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
                     compatInfo);
             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
-            return getOrCreateResources(activityToken, key, classLoader);
+
+            cleanupReferences(activityToken);
+
+            if (activityToken != null) {
+                rebaseKeyForActivity(activityToken, key);
+            }
+
+            return createResources(activityToken, key, classLoader);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
         }
@@ -944,67 +1051,40 @@
                             here);
                 }
 
-                final boolean activityHasOverrideConfig =
-                        !activityResources.overrideConfig.equals(Configuration.EMPTY);
 
                 // Rebase each Resources associated with this Activity.
                 final int refCount = activityResources.activityResources.size();
                 for (int i = 0; i < refCount; i++) {
                     WeakReference<Resources> weakResRef = activityResources.activityResources.get(
                             i);
+
                     Resources resources = weakResRef.get();
                     if (resources == null) {
                         continue;
                     }
 
-                    // Extract the ResourcesKey that was last used to create the Resources for this
-                    // activity.
-                    final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
-                    if (oldKey == null) {
-                        Slog.e(TAG, "can't find ResourcesKey for resources impl="
-                                + resources.getImpl());
+                    ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
+                            overrideConfig, displayId);
+                    updateActivityResources(resources, newKey, false);
+                }
+
+                // Also find loaders that are associated with an Activity
+                final int loaderCount = mResourcesWithLoaders.size();
+                for (int index = loaderCount - 1; index >= 0; index--) {
+                    ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(
+                            index);
+                    Resources resources = resourcesWithLoaders.resources();
+                    if (resources == null
+                            || resourcesWithLoaders.activityToken() != activityToken) {
                         continue;
                     }
 
-                    // Build the new override configuration for this ResourcesKey.
-                    final Configuration rebasedOverrideConfig = new Configuration();
-                    if (overrideConfig != null) {
-                        rebasedOverrideConfig.setTo(overrideConfig);
-                    }
+                    ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
+                            overrideConfig, displayId);
 
-                    if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
-                        // Generate a delta between the old base Activity override configuration and
-                        // the actual final override configuration that was used to figure out the
-                        // real delta this Resources object wanted.
-                        Configuration overrideOverrideConfig = Configuration.generateDelta(
-                                oldConfig, oldKey.mOverrideConfiguration);
-                        rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
-                    }
+                    updateActivityResources(resources, newKey, true);
 
-                    // Create the new ResourcesKey with the rebased override config.
-                    final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
-                            oldKey.mSplitResDirs,
-                            oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
-                            rebasedOverrideConfig, oldKey.mCompatInfo);
-
-                    if (DEBUG) {
-                        Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
-                                + " to newKey=" + newKey + ", displayId=" + displayId);
-                    }
-
-                    ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
-                    if (resourcesImpl == null) {
-                        resourcesImpl = createResourcesImpl(newKey);
-                        if (resourcesImpl != null) {
-                            mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
-                        }
-                    }
-
-                    if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
-                        // Set the ResourcesImpl, updating it for all users of this Resources
-                        // object.
-                        resources.setImpl(resourcesImpl);
-                    }
+                    resourcesWithLoaders.updateKey(newKey);
                 }
             }
         } finally {
@@ -1012,6 +1092,70 @@
         }
     }
 
+    /**
+     * Rebases an updated override config over any old override config and returns the new one
+     * that an Activity's Resources should be set to.
+     */
+    private ResourcesKey rebaseActivityOverrideConfig(Resources resources,
+            Configuration oldOverrideConfig, @Nullable Configuration newOverrideConfig,
+            int displayId) {
+        // Extract the ResourcesKey that was last used to create the Resources for this
+        // activity.
+        final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
+        if (oldKey == null) {
+            Slog.e(TAG, "can't find ResourcesKey for resources impl="
+                    + resources.getImpl());
+            return null;
+        }
+
+        // Build the new override configuration for this ResourcesKey.
+        final Configuration rebasedOverrideConfig = new Configuration();
+        if (newOverrideConfig != null) {
+            rebasedOverrideConfig.setTo(newOverrideConfig);
+        }
+
+        final boolean hadOverrideConfig = !oldOverrideConfig.equals(Configuration.EMPTY);
+        if (hadOverrideConfig && oldKey.hasOverrideConfiguration()) {
+            // Generate a delta between the old base Activity override configuration and
+            // the actual final override configuration that was used to figure out the
+            // real delta this Resources object wanted.
+            Configuration overrideOverrideConfig = Configuration.generateDelta(
+                    oldOverrideConfig, oldKey.mOverrideConfiguration);
+            rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
+        }
+
+        // Create the new ResourcesKey with the rebased override config.
+        final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
+                oldKey.mSplitResDirs,
+                oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
+                rebasedOverrideConfig, oldKey.mCompatInfo);
+
+        if (DEBUG) {
+            Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
+                    + " to newKey=" + newKey + ", displayId=" + displayId);
+        }
+
+        return newKey;
+    }
+
+    private void updateActivityResources(Resources resources, ResourcesKey newKey,
+            boolean hasLoader) {
+        final ResourcesImpl resourcesImpl;
+
+        if (hasLoader) {
+            // Loaders always get new Impls because they cannot be shared
+            resourcesImpl = createResourcesImpl(newKey);
+        } else {
+            resourcesImpl = findOrCreateResourcesImplForKeyLocked(newKey);
+        }
+
+        if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
+            // Set the ResourcesImpl, updating it for all users of this Resources
+            // object.
+            resources.setImpl(resourcesImpl);
+        }
+    }
+
     @TestApi
     public final boolean applyConfigurationToResources(@NonNull Configuration config,
             @Nullable CompatibilityInfo compat) {
@@ -1050,61 +1194,77 @@
             ApplicationPackageManager.configurationChanged();
             //Slog.i(TAG, "Configuration changed in " + currentPackageName());
 
-            Configuration tmpConfig = null;
+            Configuration tmpConfig = new Configuration();
 
             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
                 ResourcesKey key = mResourceImpls.keyAt(i);
                 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
                 ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
                 if (r != null) {
-                    if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
-                            + r + " config to: " + config);
-                    int displayId = key.mDisplayId;
-                    boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
-                    DisplayMetrics dm = defaultDisplayMetrics;
-                    final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
-                    if (!isDefaultDisplay || hasOverrideConfiguration) {
-                        if (tmpConfig == null) {
-                            tmpConfig = new Configuration();
-                        }
-                        tmpConfig.setTo(config);
-
-                        // Get new DisplayMetrics based on the DisplayAdjustments given
-                        // to the ResourcesImpl. Update a copy if the CompatibilityInfo
-                        // changed, because the ResourcesImpl object will handle the
-                        // update internally.
-                        DisplayAdjustments daj = r.getDisplayAdjustments();
-                        if (compat != null) {
-                            daj = new DisplayAdjustments(daj);
-                            daj.setCompatibilityInfo(compat);
-                        }
-                        dm = getDisplayMetrics(displayId, daj);
-
-                        if (!isDefaultDisplay) {
-                            applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
-                        }
-
-                        if (hasOverrideConfiguration) {
-                            tmpConfig.updateFrom(key.mOverrideConfiguration);
-                        }
-                        r.updateConfiguration(tmpConfig, dm, compat);
-                    } else {
-                        r.updateConfiguration(config, dm, compat);
-                    }
-                    //Slog.i(TAG, "Updated app resources " + v.getKey()
-                    //        + " " + r + ": " + r.getConfiguration());
+                    applyConfigurationToResourcesLocked(config, compat, tmpConfig,
+                            defaultDisplayMetrics, key, r);
                 } else {
-                    //Slog.i(TAG, "Removing old resources " + v.getKey());
                     mResourceImpls.removeAt(i);
                 }
             }
 
+            for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) {
+                ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+                Resources resources = resourcesWithLoaders.resources();
+                if (resources == null) {
+                    mResourcesWithLoaders.remove(index);
+                    continue;
+                }
+
+                applyConfigurationToResourcesLocked(config, compat, tmpConfig,
+                        defaultDisplayMetrics, resourcesWithLoaders.resourcesKey(),
+                        resources.getImpl());
+            }
+
             return changes != 0;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
         }
     }
 
+    private void applyConfigurationToResourcesLocked(@NonNull Configuration config,
+            @Nullable CompatibilityInfo compat, Configuration tmpConfig,
+            DisplayMetrics defaultDisplayMetrics, ResourcesKey key, ResourcesImpl resourcesImpl) {
+        if (DEBUG || DEBUG_CONFIGURATION) {
+            Slog.v(TAG, "Changing resources "
+                    + resourcesImpl + " config to: " + config);
+        }
+        int displayId = key.mDisplayId;
+        boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+        DisplayMetrics dm = defaultDisplayMetrics;
+        final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
+        if (!isDefaultDisplay || hasOverrideConfiguration) {
+            tmpConfig.setTo(config);
+
+            // Get new DisplayMetrics based on the DisplayAdjustments given
+            // to the ResourcesImpl. Update a copy if the CompatibilityInfo
+            // changed, because the ResourcesImpl object will handle the
+            // update internally.
+            DisplayAdjustments daj = resourcesImpl.getDisplayAdjustments();
+            if (compat != null) {
+                daj = new DisplayAdjustments(daj);
+                daj.setCompatibilityInfo(compat);
+            }
+            dm = getDisplayMetrics(displayId, daj);
+
+            if (!isDefaultDisplay) {
+                applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
+            }
+
+            if (hasOverrideConfiguration) {
+                tmpConfig.updateFrom(key.mOverrideConfiguration);
+            }
+            resourcesImpl.updateConfiguration(tmpConfig, dm, compat);
+        } else {
+            resourcesImpl.updateConfiguration(config, dm, compat);
+        }
+    }
+
     /**
      * Appends the library asset path to any ResourcesImpl object that contains the main
      * assetPath.
@@ -1140,7 +1300,7 @@
                                 ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
                     }
 
-                    if (newLibAssets != key.mLibDirs) {
+                    if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
                         updatedResourceKeys.put(impl, new ResourcesKey(
                                 key.mResDir,
                                 key.mSplitResDirs,
@@ -1153,10 +1313,106 @@
                 }
             }
 
+            final int count = mResourcesWithLoaders.size();
+            for (int index = 0; index < count; index++) {
+                ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+                Resources resources = resourcesWithLoaders.resources();
+                if (resources == null) {
+                    continue;
+                }
+
+                ResourcesKey key = resourcesWithLoaders.resourcesKey();
+                if (Objects.equals(key.mResDir, assetPath)) {
+                    String[] newLibAssets = key.mLibDirs;
+                    for (String libAsset : libAssets) {
+                        newLibAssets =
+                                ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
+                    }
+
+                    if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
+                        updatedResourceKeys.put(resources.getImpl(),
+                                new ResourcesKey(
+                                        key.mResDir,
+                                        key.mSplitResDirs,
+                                        key.mOverlayDirs,
+                                        newLibAssets,
+                                        key.mDisplayId,
+                                        key.mOverrideConfiguration,
+                                        key.mCompatInfo));
+                    }
+                }
+            }
+
             redirectResourcesToNewImplLocked(updatedResourceKeys);
         }
     }
 
+    /**
+     * Mark a {@link Resources} as containing a {@link ResourceLoader}.
+     *
+     * This removes its {@link ResourcesImpl} from the shared pool and creates it a new one. It is
+     * then tracked separately, kept unique, and restored properly across {@link Activity}
+     * recreations.
+     */
+    public void registerForLoaders(Resources resources) {
+        synchronized (this) {
+            boolean found = false;
+            IBinder activityToken = null;
+
+            // Remove the Resources from the reference list as it's now tracked separately
+            int size = mResourceReferences.size();
+            for (int index = 0; index < size; index++) {
+                WeakReference<Resources> reference = mResourceReferences.get(index);
+                if (reference.get() == resources) {
+                    mResourceReferences.remove(index);
+                    found = true;
+                    break;
+                }
+            }
+
+            if (!found) {
+                // Do the same removal for any Activity Resources
+                for (Map.Entry<IBinder, ActivityResources> entry :
+                        mActivityResourceReferences.entrySet()) {
+                    ArrayList<WeakReference<Resources>> activityResourcesList =
+                            entry.getValue().activityResources;
+                    final int resCount = activityResourcesList.size();
+                    for (int index = 0; index < resCount; index++) {
+                        WeakReference<Resources> reference = activityResourcesList.get(index);
+                        if (reference.get() == resources) {
+                            activityToken = entry.getKey();
+                            activityResourcesList.remove(index);
+                            found = true;
+                            break;
+                        }
+                    }
+
+                    if (found) {
+                        break;
+                    }
+                }
+            }
+
+            if (!found) {
+                throw new IllegalArgumentException("Resources " + resources
+                        + " registered for loaders but was not previously tracked by"
+                        + " ResourcesManager");
+            }
+
+            ResourcesKey key = findKeyForResourceImplLocked(resources.getImpl());
+            ResourcesImpl impl = createResourcesImpl(key);
+
+            if (mResourcesWithLoaders == Collections.EMPTY_LIST) {
+                mResourcesWithLoaders = Collections.synchronizedList(new ArrayList<>());
+            }
+
+            mResourcesWithLoaders.add(new ResourcesWithLoaders(resources, key, activityToken));
+
+            // Set the new Impl, which is now guaranteed to be unique per Resources object
+            resources.setImpl(impl);
+        }
+    }
+
     // TODO(adamlesinski): Make this accept more than just overlay directories.
     final void applyNewResourceDirsLocked(@NonNull final ApplicationInfo appInfo,
             @Nullable final String[] oldPaths) {
@@ -1201,6 +1457,32 @@
                 }
             }
 
+            final int count = mResourcesWithLoaders.size();
+            for (int index = 0; index < count; index++) {
+                ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+                Resources resources = resourcesWithLoaders.resources();
+                if (resources == null) {
+                    continue;
+                }
+
+                ResourcesKey key = resourcesWithLoaders.resourcesKey();
+
+                if (key.mResDir == null
+                        || key.mResDir.equals(baseCodePath)
+                        || ArrayUtils.contains(oldPaths, key.mResDir)) {
+                    updatedResourceKeys.put(resources.getImpl(),
+                            new ResourcesKey(
+                                    baseCodePath,
+                                    copiedSplitDirs,
+                                    copiedResourceDirs,
+                                    key.mLibDirs,
+                                    key.mDisplayId,
+                                    key.mOverrideConfiguration,
+                                    key.mCompatInfo
+                            ));
+                }
+            }
+
             redirectResourcesToNewImplLocked(updatedResourceKeys);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
@@ -1250,5 +1532,25 @@
                 }
             }
         }
+
+        // Update any references that need to be re-built with loaders. These are intentionally not
+        // part of either of the above lists.
+        final int loaderCount = mResourcesWithLoaders.size();
+        for (int index = loaderCount - 1; index >= 0; index--) {
+            ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+            Resources resources = resourcesWithLoaders.resources();
+            if (resources == null) {
+                continue;
+            }
+
+            ResourcesKey newKey = updatedResourceKeys.get(resources.getImpl());
+            if (newKey == null) {
+                continue;
+            }
+
+            resourcesWithLoaders.updateKey(newKey);
+            resourcesWithLoaders.resources()
+                    .setImpl(createResourcesImpl(newKey));
+        }
     }
 }
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 9929855..d9bfde5 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -16,45 +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.util.Preconditions;
 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;
@@ -62,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
@@ -204,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);
@@ -212,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) {
@@ -226,21 +182,21 @@
     };
 
     /**
-     * Returnsthe {@code PasswordMetrics} for a given credential.
+     * Returns the {@code PasswordMetrics} for a given credential.
      *
      * If the credential is a pin or a password, equivalent to {@link #computeForPassword(byte[])}.
      * {@code credential} cannot be null when {@code type} is
      * {@link com.android.internal.widget.LockPatternUtils#CREDENTIAL_TYPE_PASSWORD}.
      */
-    public static PasswordMetrics computeForCredential(
-            @CredentialType int type, byte[] credential) {
-        if (type == CREDENTIAL_TYPE_PASSWORD) {
-            Preconditions.checkNotNull(credential, "credential cannot be null");
-            return PasswordMetrics.computeForPassword(credential);
-        } else if (type == CREDENTIAL_TYPE_PATTERN)  {
-            return new PasswordMetrics(PASSWORD_QUALITY_SOMETHING);
-        } else /* if (type == CREDENTIAL_TYPE_NONE) */ {
-            return new PasswordMetrics(PASSWORD_QUALITY_UNSPECIFIED);
+    public static PasswordMetrics computeForCredential(LockscreenCredential credential) {
+        if (credential.isPassword()) {
+            return PasswordMetrics.computeForPassword(credential.getCredential());
+        } else if (credential.isPattern())  {
+            return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+        } else if (credential.isNone()) {
+            return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+        } else {
+            throw new IllegalArgumentException("Unknown credential type " + credential.getType());
         }
     }
 
@@ -255,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++;
@@ -273,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);
     }
 
     /**
@@ -404,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/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java
index 955093d..90ecce2 100644
--- a/core/java/android/app/slice/SliceManager.java
+++ b/core/java/android/app/slice/SliceManager.java
@@ -390,6 +390,8 @@
             }
             Bundle extras = new Bundle();
             extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
+            extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
+                    new ArrayList<>(supportedSpecs));
             final Bundle res = provider.call(SliceProvider.METHOD_MAP_INTENT, null, extras);
             if (res == null) {
                 return null;
diff --git a/core/java/android/app/timedetector/TimeSignal.java b/core/java/android/app/timedetector/TimeSignal.java
index da21794..b494260 100644
--- a/core/java/android/app/timedetector/TimeSignal.java
+++ b/core/java/android/app/timedetector/TimeSignal.java
@@ -56,8 +56,7 @@
 
     private static TimeSignal createFromParcel(Parcel in) {
         String sourceId = in.readString();
-        TimestampedValue<Long> utcTime =
-                TimestampedValue.readFromParcel(in, null /* classLoader */, Long.class);
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
         return new TimeSignal(sourceId, utcTime);
     }
 
@@ -69,7 +68,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mSourceId);
-        TimestampedValue.writeToParcel(dest, mUtcTime);
+        dest.writeParcelable(mUtcTime, 0);
     }
 
     @NonNull
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2dde3ae..e7e278f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1559,7 +1559,14 @@
      * @see Environment#getExternalStorageState(File)
      * @see Environment#isExternalStorageEmulated(File)
      * @see Environment#isExternalStorageRemovable(File)
+     * @deprecated These directories still exist and are scanned, but developers
+     *             are encouraged to migrate to inserting content into a
+     *             {@link MediaStore} collection directly, as any app can
+     *             contribute new media to {@link MediaStore} with no
+     *             permissions required, starting in
+     *             {@link android.os.Build.VERSION_CODES#Q}.
      */
+    @Deprecated
     public abstract File[] getExternalMediaDirs();
 
     /**
@@ -5237,7 +5244,7 @@
     @SystemApi
     @TestApi
     @NonNull
-    public Context createContextAsUser(@NonNull UserHandle user) {
+    public Context createContextAsUser(@NonNull UserHandle user, @CreatePackageOptions int flags) {
         if (Build.IS_ENG) {
             throw new IllegalStateException("createContextAsUser not overridden!");
         }
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index f7cd51e..7993ea1 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -885,8 +885,8 @@
 
     /** @hide */
     @Override
-    public Context createContextAsUser(UserHandle user) {
-        return mBase.createContextAsUser(user);
+    public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) {
+        return mBase.createContextAsUser(user, flags);
     }
 
     /** @hide */
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 853e818..a2f8886 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -166,9 +166,8 @@
     }
 
     /**
-     * Returns information about all overlays for the given target package for
-     * the specified user. The returned list is ordered according to the
-     * overlay priority with the highest priority at the end of the list.
+     * Clear part of the overlay manager's internal cache of PackageInfo
+     * objects. Only intended for testing.
      *
      * @param targetPackageName The name of the target package.
      * @param user The user to get the OverlayInfos for.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 19d8edf..1d78e2c 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -682,6 +682,8 @@
 
     String getWellbeingPackageName();
 
+    String[] getTelephonyPackageNames();
+
     String getAppPredictionServicePackageName();
 
     String getSystemCaptionsServicePackageName();
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 69ce3bd..edc66c5 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -349,14 +349,7 @@
      */
     public int createSession(@NonNull SessionParams params) throws IOException {
         try {
-            final String installerPackage;
-            if (params.installerPackageName == null) {
-                installerPackage = mInstallerPackageName;
-            } else {
-                installerPackage = params.installerPackageName;
-            }
-
-            return mInstaller.createSession(params, installerPackage, mUserId);
+            return mInstaller.createSession(params, mInstallerPackageName, mUserId);
         } catch (RuntimeException e) {
             ExceptionUtils.maybeUnwrapIOException(e);
             throw e;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9513ce8..7509065 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2858,6 +2858,14 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+     * The device does not have slices implementation.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_SLICES_DISABLED = "android.software.slices_disabled";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
      * The device supports device-unique Keystore attestations.  Only available on devices that
      * also support {@link #FEATURE_STRONGBOX_KEYSTORE}, and can only be used by device owner
      * apps (see {@link android.app.admin.DevicePolicyManager#generateKeyPair}).
@@ -7416,6 +7424,18 @@
     }
 
     /**
+     * @return the system defined telephony package names, or null if there's none.
+     *
+     * @hide
+     */
+    @Nullable
+    @TestApi
+    public String[] getTelephonyPackageNames() {
+        throw new UnsupportedOperationException(
+                "getTelephonyPackageNames not implemented in subclass");
+    }
+
+    /**
      * @return the system defined content capture service package name, or null if there's none.
      *
      * @hide
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 0b157fa..cf21e96 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -577,8 +577,6 @@
      */
     public interface Callback {
         boolean hasFeature(String feature);
-        String[] getOverlayPaths(String targetPackageName, String targetPath);
-        String[] getOverlayApks(String targetPackageName);
     }
 
     /**
@@ -595,14 +593,6 @@
         @Override public boolean hasFeature(String feature) {
             return mPm.hasSystemFeature(feature);
         }
-
-        @Override public String[] getOverlayPaths(String targetPackageName, String targetPath) {
-            return null;
-        }
-
-        @Override public String[] getOverlayApks(String targetPackageName) {
-            return null;
-        }
     }
 
     /**
@@ -1195,19 +1185,7 @@
             }
 
             final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath());
-            Package p = fromCacheEntry(bytes);
-            if (mCallback != null) {
-                String[] overlayApks = mCallback.getOverlayApks(p.packageName);
-                if (overlayApks != null && overlayApks.length > 0) {
-                    for (String overlayApk : overlayApks) {
-                        // If a static RRO is updated, return null.
-                        if (!isCacheUpToDate(new File(overlayApk), cacheFile)) {
-                            return null;
-                        }
-                    }
-                }
-            }
-            return p;
+            return fromCacheEntry(bytes);
         } catch (Throwable e) {
             Slog.w(TAG, "Error reading package cache: ", e);
 
@@ -1381,7 +1359,7 @@
             final Resources res = new Resources(assets, mMetrics, null);
 
             final String[] outError = new String[1];
-            final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
+            final Package pkg = parseBaseApk(res, parser, flags, outError);
             if (pkg == null) {
                 throw new PackageParserException(mParseError,
                         apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
@@ -1944,7 +1922,6 @@
      * need to consider whether they should be supported by split APKs and child
      * packages.
      *
-     * @param apkPath The package apk file path
      * @param res The resources from which to resolve values
      * @param parser The manifest parser
      * @param flags Flags how to parse
@@ -1954,8 +1931,7 @@
      * @throws XmlPullParserException
      * @throws IOException
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
+    private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
             String[] outError) throws XmlPullParserException, IOException {
         final String splitName;
         final String pkgName;
@@ -1975,15 +1951,6 @@
             return null;
         }
 
-        if (mCallback != null) {
-            String[] overlayPaths = mCallback.getOverlayPaths(pkgName, apkPath);
-            if (overlayPaths != null && overlayPaths.length > 0) {
-                for (String overlayPath : overlayPaths) {
-                    res.getAssets().addOverlayPath(overlayPath);
-                }
-            }
-        }
-
         final Package pkg = new Package(pkgName);
 
         TypedArray sa = res.obtainAttributes(parser,
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index dd5c6a5..aa6f58e 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -237,6 +237,28 @@
     @TestApi
     public static final int PROTECTION_FLAG_APP_PREDICTOR = 0x200000;
 
+    /**
+     * Additional flag for {@link #protectionLevel}, corresponding
+     * to the <code>telephony</code> value of
+     * {@link android.R.attr#protectionLevel}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int PROTECTION_FLAG_TELEPHONY = 0x400000;
+
+    /**
+     * Additional flag for {@link #protectionLevel}, corresponding
+     * to the <code>wifi</code> value of
+     * {@link android.R.attr#protectionLevel}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int PROTECTION_FLAG_WIFI = 0x800000;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
             PROTECTION_FLAG_PRIVILEGED,
@@ -258,6 +280,8 @@
             PROTECTION_FLAG_CONFIGURATOR,
             PROTECTION_FLAG_INCIDENT_REPORT_APPROVER,
             PROTECTION_FLAG_APP_PREDICTOR,
+            PROTECTION_FLAG_TELEPHONY,
+            PROTECTION_FLAG_WIFI,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProtectionFlags {}
@@ -501,6 +525,12 @@
         if ((level & PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR) != 0) {
             protLevel += "|appPredictor";
         }
+        if ((level & PermissionInfo.PROTECTION_FLAG_TELEPHONY) != 0) {
+            protLevel += "|telephony";
+        }
+        if ((level & PermissionInfo.PROTECTION_FLAG_WIFI) != 0) {
+            protLevel += "|wifi";
+        }
         return protLevel;
     }
 
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index a35ad56..de1d514 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -16,7 +16,10 @@
 package android.content.res;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
+import android.content.res.loader.ResourcesProvider;
+import android.text.TextUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
@@ -36,10 +39,14 @@
  */
 public final class ApkAssets {
     @GuardedBy("this") private final long mNativePtr;
+
+    @Nullable
     @GuardedBy("this") private final StringBlock mStringBlock;
 
     @GuardedBy("this") private boolean mOpen = true;
 
+    private final boolean mForLoader;
+
     /**
      * Creates a new ApkAssets instance from the given path on disk.
      *
@@ -48,7 +55,8 @@
      * @throws IOException if a disk I/O error or parsing error occurred.
      */
     public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
-        return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/);
+        return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/,
+                false /*arscOnly*/, false /*forLoader*/);
     }
 
     /**
@@ -61,7 +69,8 @@
      */
     public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system)
             throws IOException {
-        return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/);
+        return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/,
+                false /*arscOnly*/, false /*forLoader*/);
     }
 
     /**
@@ -76,7 +85,8 @@
      */
     public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system,
             boolean forceSharedLibrary) throws IOException {
-        return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/);
+        return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/,
+                false /*arscOnly*/, false /*forLoader*/);
     }
 
     /**
@@ -96,7 +106,8 @@
     public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
             @NonNull String friendlyName, boolean system, boolean forceSharedLibrary)
             throws IOException {
-        return new ApkAssets(fd, friendlyName, system, forceSharedLibrary);
+        return new ApkAssets(fd, friendlyName, system, forceSharedLibrary, false /*arscOnly*/,
+                false /*forLoader*/);
     }
 
     /**
@@ -110,21 +121,90 @@
      */
     public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system)
             throws IOException {
-        return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/);
+        return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/,
+                false /*arscOnly*/, false /*forLoader*/);
     }
 
-    private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+    /**
+     * Creates a new ApkAssets instance from the given path on disk for use with a
+     * {@link ResourcesProvider}.
+     *
+     * @param path The path to an APK on disk.
+     * @return a new instance of ApkAssets.
+     * @throws IOException if a disk I/O error or parsing error occurred.
+     */
+    public static @NonNull ApkAssets loadApkForLoader(@NonNull String path)
             throws IOException {
+        return new ApkAssets(path, false /*system*/, false /*forceSharedLibrary*/,
+                false /*overlay*/, false /*arscOnly*/, true /*forLoader*/);
+    }
+
+    /**
+     * Creates a new ApkAssets instance from the given file descriptor for use with a
+     * {@link ResourcesProvider}.
+     *
+     * Performs a dup of the underlying fd, so you must take care of still closing
+     * the FileDescriptor yourself (and can do that whenever you want).
+     *
+     * @param fd The FileDescriptor of an open, readable APK.
+     * @return a new instance of ApkAssets.
+     * @throws IOException if a disk I/O error or parsing error occurred.
+     */
+    @NonNull
+    public static ApkAssets loadApkForLoader(@NonNull FileDescriptor fd) throws IOException {
+        return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()),
+                false /*system*/, false /*forceSharedLib*/, false /*arscOnly*/, true /*forLoader*/);
+    }
+
+    /**
+     * Creates a new ApkAssets instance from the given file descriptor representing an ARSC
+     * for use with a {@link ResourcesProvider}.
+     *
+     * Performs a dup of the underlying fd, so you must take care of still closing
+     * the FileDescriptor yourself (and can do that whenever you want).
+     *
+     * @param fd The FileDescriptor of an open, readable .arsc.
+     * @return a new instance of ApkAssets.
+     * @throws IOException if a disk I/O error or parsing error occurred.
+     */
+    public static @NonNull ApkAssets loadArscForLoader(@NonNull FileDescriptor fd)
+            throws IOException {
+        return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()),
+                false /*system*/, false /*forceSharedLib*/, true /*arscOnly*/, true /*forLoader*/);
+    }
+
+    /**
+     * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence
+     * is required for a lot of APIs, and it's easier to have a non-null reference rather than
+     * tracking a separate identifier.
+     */
+    @NonNull
+    public static ApkAssets loadEmptyForLoader() {
+        return new ApkAssets(true);
+    }
+
+    private ApkAssets(boolean forLoader) {
+        mForLoader = forLoader;
+        mNativePtr = nativeLoadEmpty(forLoader);
+        mStringBlock = null;
+    }
+
+    private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay,
+            boolean arscOnly, boolean forLoader) throws IOException {
+        mForLoader = forLoader;
         Preconditions.checkNotNull(path, "path");
-        mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
+        mNativePtr = arscOnly ? nativeLoadArsc(path, forLoader)
+                : nativeLoad(path, system, forceSharedLib, overlay, forLoader);
         mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
     }
 
     private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system,
-            boolean forceSharedLib) throws IOException {
+            boolean forceSharedLib, boolean arscOnly, boolean forLoader) throws IOException {
+        mForLoader = forLoader;
         Preconditions.checkNotNull(fd, "fd");
         Preconditions.checkNotNull(friendlyName, "friendlyName");
-        mNativePtr = nativeLoadFromFd(fd, friendlyName, system, forceSharedLib);
+        mNativePtr = arscOnly ? nativeLoadArscFromFd(fd, friendlyName, forLoader)
+                : nativeLoadFromFd(fd, friendlyName, system, forceSharedLib, forLoader);
         mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
     }
 
@@ -136,11 +216,19 @@
     }
 
     CharSequence getStringFromPool(int idx) {
+        if (mStringBlock == null) {
+            return null;
+        }
+
         synchronized (this) {
             return mStringBlock.get(idx);
         }
     }
 
+    public boolean isForLoader() {
+        return mForLoader;
+    }
+
     /**
      * Retrieve a parser for a compiled XML file. This is associated with a single APK and
      * <em>NOT</em> a full AssetManager. This means that shared-library references will not be
@@ -192,18 +280,26 @@
         synchronized (this) {
             if (mOpen) {
                 mOpen = false;
-                mStringBlock.close();
+                if (mStringBlock != null) {
+                    mStringBlock.close();
+                }
                 nativeDestroy(mNativePtr);
             }
         }
     }
 
-    private static native long nativeLoad(
-            @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+    private static native long nativeLoad(@NonNull String path, boolean system,
+            boolean forceSharedLib, boolean overlay, boolean forLoader)
             throws IOException;
     private static native long nativeLoadFromFd(@NonNull FileDescriptor fd,
-            @NonNull String friendlyName, boolean system, boolean forceSharedLib)
+            @NonNull String friendlyName, boolean system, boolean forceSharedLib,
+            boolean forLoader)
             throws IOException;
+    private static native long nativeLoadArsc(@NonNull String path, boolean forLoader)
+            throws IOException;
+    private static native long nativeLoadArscFromFd(@NonNull FileDescriptor fd,
+            @NonNull String friendlyName, boolean forLoader) throws IOException;
+    private static native long nativeLoadEmpty(boolean forLoader);
     private static native void nativeDestroy(long ptr);
     private static native @NonNull String nativeGetAssetPath(long ptr);
     private static native long nativeGetStringBlock(long ptr);
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 7d6dc97..23e7720 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -27,9 +27,13 @@
 import android.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration.NativeConfig;
+import android.content.res.loader.ResourceLoader;
+import android.content.res.loader.ResourceLoaderManager;
+import android.content.res.loader.ResourcesProvider;
 import android.os.ParcelFileDescriptor;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.util.TypedValue;
 
@@ -39,15 +43,19 @@
 import libcore.io.IoUtils;
 
 import java.io.BufferedReader;
+import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.nio.channels.FileLock;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 /**
@@ -110,6 +118,13 @@
     @GuardedBy("this") private int mNumRefs = 1;
     @GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks;
 
+    private ResourceLoaderManager mResourceLoaderManager;
+
+    /** @hide */
+    public void setResourceLoaderManager(ResourceLoaderManager resourceLoaderManager) {
+        mResourceLoaderManager = resourceLoaderManager;
+    }
+
     /**
      * A Builder class that helps create an AssetManager with only a single invocation of
      * {@link AssetManager#setApkAssets(ApkAssets[], boolean)}. Without using this builder,
@@ -507,7 +522,7 @@
                     outValue.changingConfigurations);
 
             if (outValue.type == TypedValue.TYPE_STRING) {
-                outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+                outValue.string = getPooledStringForCookie(cookie, outValue.data);
             }
             return true;
         }
@@ -554,7 +569,7 @@
                     outValue.changingConfigurations);
 
             if (outValue.type == TypedValue.TYPE_STRING) {
-                return mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+                return getPooledStringForCookie(cookie, outValue.data);
             }
             return outValue.coerceToString();
         }
@@ -632,7 +647,7 @@
                 int cookie = rawInfoArray[i];
                 int index = rawInfoArray[i + 1];
                 retArray[j] = (index >= 0 && cookie > 0)
-                        ? mApkAssets[cookie - 1].getStringFromPool(index) : null;
+                        ? getPooledStringForCookie(cookie, index) : null;
             }
             return retArray;
         }
@@ -688,7 +703,7 @@
                     outValue.changingConfigurations);
 
             if (outValue.type == TypedValue.TYPE_STRING) {
-                outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+                outValue.string = getPooledStringForCookie(cookie, outValue.data);
             }
             return true;
         }
@@ -753,6 +768,7 @@
      *
      * @hide
      */
+    @TestApi
     public void setResourceResolutionLoggingEnabled(boolean enabled) {
         synchronized (this) {
             ensureValidLocked();
@@ -768,6 +784,7 @@
      *
      * @hide
      */
+    @TestApi
     public @Nullable String getLastResourceResolution() {
         synchronized (this) {
             ensureValidLocked();
@@ -814,6 +831,13 @@
         Preconditions.checkNotNull(fileName, "fileName");
         synchronized (this) {
             ensureOpenLocked();
+
+            String path = Paths.get("assets", fileName).toString();
+            InputStream inputStream = searchLoaders(0, path, accessMode);
+            if (inputStream != null) {
+                return inputStream;
+            }
+
             final long asset = nativeOpenAsset(mObject, fileName, accessMode);
             if (asset == 0) {
                 throw new FileNotFoundException("Asset file: " + fileName);
@@ -838,6 +862,13 @@
         Preconditions.checkNotNull(fileName, "fileName");
         synchronized (this) {
             ensureOpenLocked();
+
+            String path = Paths.get("assets", fileName).toString();
+            AssetFileDescriptor fileDescriptor = searchLoadersFd(0, path);
+            if (fileDescriptor != null) {
+                return fileDescriptor;
+            }
+
             final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets);
             if (pfd == null) {
                 throw new FileNotFoundException("Asset file: " + fileName);
@@ -931,6 +962,12 @@
         Preconditions.checkNotNull(fileName, "fileName");
         synchronized (this) {
             ensureOpenLocked();
+
+            InputStream inputStream = searchLoaders(cookie, fileName, accessMode);
+            if (inputStream != null) {
+                return inputStream;
+            }
+
             final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode);
             if (asset == 0) {
                 throw new FileNotFoundException("Asset absolute file: " + fileName);
@@ -970,6 +1007,12 @@
         Preconditions.checkNotNull(fileName, "fileName");
         synchronized (this) {
             ensureOpenLocked();
+
+            AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName);
+            if (fileDescriptor != null) {
+                return fileDescriptor;
+            }
+
             final ParcelFileDescriptor pfd =
                     nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets);
             if (pfd == null) {
@@ -1031,7 +1074,16 @@
         Preconditions.checkNotNull(fileName, "fileName");
         synchronized (this) {
             ensureOpenLocked();
-            final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
+
+            final long xmlBlock;
+            AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName);
+            if (fileDescriptor != null) {
+                xmlBlock = nativeOpenXmlAssetFd(mObject, cookie,
+                        fileDescriptor.getFileDescriptor());
+            } else {
+                xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
+            }
+
             if (xmlBlock == 0) {
                 throw new FileNotFoundException("Asset XML file: " + fileName);
             }
@@ -1041,6 +1093,85 @@
         }
     }
 
+    private InputStream searchLoaders(int cookie, @NonNull String fileName, int accessMode)
+            throws IOException {
+        if (mResourceLoaderManager == null) {
+            return null;
+        }
+
+        List<Pair<ResourceLoader, ResourcesProvider>> loaders =
+                mResourceLoaderManager.getInternalList();
+
+        // A cookie of 0 means no specific ApkAssets, so search everything
+        if (cookie == 0) {
+            for (int index = loaders.size() - 1; index >= 0; index--) {
+                Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+                try {
+                    InputStream inputStream = pair.first.loadAsset(fileName, accessMode);
+                    if (inputStream != null) {
+                        return inputStream;
+                    }
+                } catch (IOException ignored) {
+                    // When searching, ignore read failures
+                }
+            }
+
+            return null;
+        }
+
+        ApkAssets apkAssets = mApkAssets[cookie - 1];
+        for (int index = loaders.size() - 1; index >= 0; index--) {
+            Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+            if (pair.second.getApkAssets() == apkAssets) {
+                return pair.first.loadAsset(fileName, accessMode);
+            }
+        }
+
+        return null;
+    }
+
+    private AssetFileDescriptor searchLoadersFd(int cookie, @NonNull String fileName)
+            throws IOException {
+        if (mResourceLoaderManager == null) {
+            return null;
+        }
+
+        List<Pair<ResourceLoader, ResourcesProvider>> loaders =
+                mResourceLoaderManager.getInternalList();
+
+        // A cookie of 0 means no specific ApkAssets, so search everything
+        if (cookie == 0) {
+            for (int index = loaders.size() - 1; index >= 0; index--) {
+                Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+                try {
+                    ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
+                    if (fileDescriptor != null) {
+                        return new AssetFileDescriptor(fileDescriptor, 0,
+                                AssetFileDescriptor.UNKNOWN_LENGTH);
+                    }
+                } catch (IOException ignored) {
+                    // When searching, ignore read failures
+                }
+            }
+
+            return null;
+        }
+
+        ApkAssets apkAssets = mApkAssets[cookie - 1];
+        for (int index = loaders.size() - 1; index >= 0; index--) {
+            Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+            if (pair.second.getApkAssets() == apkAssets) {
+                ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
+                if (fileDescriptor != null) {
+                    return new AssetFileDescriptor(fileDescriptor, 0,
+                            AssetFileDescriptor.UNKNOWN_LENGTH);
+                }
+                return null;
+            }
+        }
+        return null;
+    }
+
     void xmlBlockGone(int id) {
         synchronized (this) {
             decRefsLocked(id);
@@ -1296,7 +1427,7 @@
      *
      * <p>On SDK 21 (Android 5.0: Lollipop) and above, Locale strings are valid
      * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> language tags and can be
-     * parsed using {@link java.util.Locale#forLanguageTag(String)}.
+     * parsed using {@link Locale#forLanguageTag(String)}.
      *
      * <p>On SDK 20 (Android 4.4W: Kitkat for watches) and below, locale strings
      * are of the form {@code ll_CC} where {@code ll} is a two letter language code,
@@ -1439,6 +1570,8 @@
     private static native @Nullable ParcelFileDescriptor nativeOpenNonAssetFd(long ptr, int cookie,
             @NonNull String fileName, @NonNull long[] outOffsets) throws IOException;
     private static native long nativeOpenXmlAsset(long ptr, int cookie, @NonNull String fileName);
+    private static native long nativeOpenXmlAssetFd(long ptr, int cookie,
+            @NonNull FileDescriptor fileDescriptor);
 
     // Primitive resource native methods.
     private static native int nativeGetResourceValue(long ptr, @AnyRes int resId, short density,
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index d7e4e14..2698c2d 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -30,6 +30,7 @@
 import android.annotation.DrawableRes;
 import android.annotation.FontRes;
 import android.annotation.FractionRes;
+import android.annotation.IntRange;
 import android.annotation.IntegerRes;
 import android.annotation.LayoutRes;
 import android.annotation.NonNull;
@@ -41,8 +42,12 @@
 import android.annotation.StyleableRes;
 import android.annotation.UnsupportedAppUsage;
 import android.annotation.XmlRes;
+import android.app.ResourcesManager;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
+import android.content.res.loader.ResourceLoader;
+import android.content.res.loader.ResourceLoaderManager;
+import android.content.res.loader.ResourcesProvider;
 import android.graphics.Movie;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
@@ -54,13 +59,16 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.LongSparseArray;
+import android.util.Pair;
 import android.util.Pools.SynchronizedPool;
 import android.util.TypedValue;
 import android.view.DisplayAdjustments;
 import android.view.ViewDebug;
 import android.view.ViewHierarchyEncoder;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
 import com.android.internal.util.XmlUtils;
 
@@ -71,6 +79,8 @@
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Class for accessing an application's resources.  This sits on top of the
@@ -133,6 +143,11 @@
     @UnsupportedAppUsage
     final ClassLoader mClassLoader;
 
+    private final Object mResourceLoaderLock = new Object();
+
+    @GuardedBy("mResourceLoaderLock")
+    private ResourceLoaderManager mResourceLoaderManager;
+
     /**
      * WeakReferences to Themes that were constructed from this Resources object.
      * We keep track of these in case our underlying implementation is changed, in which case
@@ -148,6 +163,8 @@
     private static final int MIN_THEME_REFS_FLUSH_SIZE = 32;
     private int mThemeRefsNextFlushSize = MIN_THEME_REFS_FLUSH_SIZE;
 
+    private int mBaseApkAssetsSize;
+
     /**
      * Returns the most appropriate default theme for the specified target SDK version.
      * <ul>
@@ -283,8 +300,15 @@
             return;
         }
 
+        mBaseApkAssetsSize = ArrayUtils.size(impl.getAssets().getApkAssets());
         mResourcesImpl = impl;
 
+        synchronized (mResourceLoaderLock) {
+            if (mResourceLoaderManager != null) {
+                mResourceLoaderManager.onImplUpdate(mResourcesImpl);
+            }
+        }
+
         // Create new ThemeImpls that are identical to the ones we have.
         synchronized (mThemeRefs) {
             final int count = mThemeRefs.size();
@@ -903,7 +927,7 @@
         try {
             final ResourcesImpl impl = mResourcesImpl;
             impl.getValueForDensity(id, density, value, true);
-            return impl.loadDrawable(this, value, id, density, theme);
+            return loadDrawable(value, id, density, theme);
         } finally {
             releaseTempTypedValue(value);
         }
@@ -913,6 +937,14 @@
     @UnsupportedAppUsage
     Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme)
             throws NotFoundException {
+        ResourceLoader loader = findLoader(value.assetCookie);
+        if (loader != null) {
+            Drawable drawable = loader.loadDrawable(value, id, density, theme);
+            if (drawable != null) {
+                return drawable;
+            }
+        }
+
         return mResourcesImpl.loadDrawable(this, value, id, density, theme);
     }
 
@@ -2280,7 +2312,7 @@
             final ResourcesImpl impl = mResourcesImpl;
             impl.getValue(id, value, true);
             if (value.type == TypedValue.TYPE_STRING) {
-                return impl.loadXmlResourceParser(value.string.toString(), id,
+                return loadXmlResourceParser(value.string.toString(), id,
                         value.assetCookie, type);
             }
             throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
@@ -2304,6 +2336,14 @@
     @UnsupportedAppUsage
     XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie,
                                             String type) throws NotFoundException {
+        ResourceLoader loader = findLoader(assetCookie);
+        if (loader != null) {
+            XmlResourceParser xml = loader.loadXmlResourceParser(file, id);
+            if (xml != null) {
+                return xml;
+            }
+        }
+
         return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type);
     }
 
@@ -2329,4 +2369,137 @@
         }
         return theme.obtainStyledAttributes(set, attrs, 0, 0);
     }
+
+    private ResourceLoader findLoader(int assetCookie) {
+        ApkAssets[] apkAssetsArray = mResourcesImpl.getAssets().getApkAssets();
+        int apkAssetsIndex = assetCookie - 1;
+        if (apkAssetsIndex < apkAssetsArray.length && apkAssetsIndex >= 0) {
+            ApkAssets apkAssets = apkAssetsArray[apkAssetsIndex];
+            if (apkAssets.isForLoader()) {
+                List<Pair<ResourceLoader, ResourcesProvider>> loaders;
+                // Since we don't lock the entire resolution path anyways,
+                // only lock here instead of entire method. The list is copied
+                // and effectively a snapshot is used.
+                synchronized (mResourceLoaderLock) {
+                    loaders = mResourceLoaderManager.getInternalList();
+                }
+
+                if (!ArrayUtils.isEmpty(loaders)) {
+                    int size = loaders.size();
+                    for (int index = 0; index < size; index++) {
+                        Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+                        if (pair.second.getApkAssets() == apkAssets) {
+                            return pair.first;
+                        }
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @return copied list of loaders and providers previously added
+     */
+    @NonNull
+    public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() {
+        synchronized (mResourceLoaderLock) {
+            return mResourceLoaderManager == null
+                    ? Collections.emptyList()
+                    : mResourceLoaderManager.getLoaders();
+        }
+    }
+
+    /**
+     * Add a custom {@link ResourceLoader} which is added to the paths searched by
+     * {@link AssetManager} when resolving a resource.
+     *
+     * Resources are resolved as if the loader was a resource overlay, meaning the latest
+     * in the list, of equal or better config, is returned.
+     *
+     * {@link ResourcesProvider}s passed in here are not managed and a reference should be held
+     * to remove, re-use, or close them when necessary.
+     *
+     * @param resourceLoader an interface used to resolve file paths for drawables/XML files;
+     *                       a reference should be kept to remove the loader if necessary
+     * @param resourcesProvider an .apk or .arsc file representation
+     * @param index where to add the loader in the list
+     * @throws IllegalArgumentException if the resourceLoader is already added
+     * @throws IndexOutOfBoundsException if the index is invalid
+     */
+    public void addLoader(@NonNull ResourceLoader resourceLoader,
+            @NonNull ResourcesProvider resourcesProvider, @IntRange(from = 0) int index) {
+        synchronized (mResourceLoaderLock) {
+            if (mResourceLoaderManager == null) {
+                ResourcesManager.getInstance().registerForLoaders(this);
+                mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl);
+            }
+
+            mResourceLoaderManager.addLoader(resourceLoader, resourcesProvider, index);
+        }
+    }
+
+    /**
+     * @see #addLoader(ResourceLoader, ResourcesProvider, int).
+     *
+     * Adds to the end of the list.
+     *
+     * @return index the loader was added at
+     */
+    public int addLoader(@NonNull ResourceLoader resourceLoader,
+            @NonNull ResourcesProvider resourcesProvider) {
+        synchronized (mResourceLoaderLock) {
+            int index = getLoaders().size();
+            addLoader(resourceLoader, resourcesProvider, index);
+            return index;
+        }
+    }
+
+    /**
+     * Remove a loader previously added by
+     * {@link #addLoader(ResourceLoader, ResourcesProvider, int)}
+     *
+     * The caller maintains responsibility for holding a reference to the matching
+     * {@link ResourcesProvider} and closing it after this method has been called.
+     *
+     * @param resourceLoader the same reference passed into [addLoader
+     * @return the index the loader was at in the list, or -1 if the loader was not found
+     */
+    public int removeLoader(@NonNull ResourceLoader resourceLoader) {
+        synchronized (mResourceLoaderLock) {
+            if (mResourceLoaderManager == null) {
+                return -1;
+            }
+
+            return mResourceLoaderManager.removeLoader(resourceLoader);
+        }
+    }
+
+    /**
+     * Swap the current set of loaders. Preferred to multiple remove/add calls as this doesn't
+     * update the resource data structures after each modification.
+     *
+     * Set to null or an empty list to clear the set of loaders.
+     *
+     * The caller maintains responsibility for holding references to the added
+     * {@link ResourcesProvider}s and closing them after this method has been called.
+     *
+     * @param resourceLoadersAndProviders a list of pairs to add
+     */
+    public void setLoaders(
+            @Nullable List<Pair<ResourceLoader, ResourcesProvider>> resourceLoadersAndProviders) {
+        synchronized (mResourceLoaderLock) {
+            if (mResourceLoaderManager == null) {
+                if (ArrayUtils.isEmpty(resourceLoadersAndProviders)) {
+                    return;
+                }
+
+                ResourcesManager.getInstance().registerForLoaders(this);
+                mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl);
+            }
+
+            mResourceLoaderManager.setLoaders(resourceLoadersAndProviders);
+        }
+    }
 }
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index b72544c..84489cf 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -57,6 +57,8 @@
 
 import com.android.internal.util.GrowingArrayUtils;
 
+import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -376,7 +378,7 @@
         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
         try {
             synchronized (mAccessLock) {
-                if (false) {
+                if (DEBUG_CONFIG) {
                     Slog.i(TAG, "**** Updating config of " + this + ": old config is "
                             + mConfiguration + " old compat is "
                             + mDisplayAdjustments.getCompatibilityInfo());
@@ -572,6 +574,20 @@
         }
     }
 
+    /**
+     * Wipe all caches that might be read and return an outdated object when resolving a resource.
+     */
+    public void clearAllCaches() {
+        synchronized (mAccessLock) {
+            mDrawableCache.clear();
+            mColorDrawableCache.clear();
+            mComplexColorCache.clear();
+            mAnimatorCache.clear();
+            mStateListAnimatorCache.clear();
+            flushLayoutCache();
+        }
+    }
+
     @Nullable
     Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
             int density, @Nullable Resources.Theme theme)
@@ -802,6 +818,27 @@
     }
 
     /**
+     * Loads a Drawable from an encoded image stream, or null.
+     *
+     * This call will handle closing the {@link InputStream}.
+     */
+    @Nullable
+    private Drawable decodeImageDrawable(@NonNull InputStream inputStream,
+            @NonNull Resources wrapper, @NonNull TypedValue value) {
+        ImageDecoder.Source src = ImageDecoder.createSource(wrapper, inputStream, value.density);
+        try {
+            return ImageDecoder.decodeDrawable(src, (decoder, info, s) ->
+                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE));
+        } catch (IOException ignored) {
+            // This is okay. This may be something that ImageDecoder does not
+            // support, like SVG.
+            return null;
+        } finally {
+            IoUtils.closeQuietly(inputStream);
+        }
+    }
+
+    /**
      * Loads a drawable from XML or resources stream.
      *
      * @return Drawable, or null if Drawable cannot be decoded.
@@ -865,8 +902,12 @@
                 } else {
                     final InputStream is = mAssets.openNonAsset(
                             value.assetCookie, file, AssetManager.ACCESS_STREAMING);
-                    AssetInputStream ais = (AssetInputStream) is;
-                    dr = decodeImageDrawable(ais, wrapper, value);
+                    if (is instanceof AssetInputStream) {
+                        AssetInputStream ais = (AssetInputStream) is;
+                        dr = decodeImageDrawable(ais, wrapper, value);
+                    } else {
+                        dr = decodeImageDrawable(is, wrapper, value);
+                    }
                 }
             } finally {
                 stack.pop();
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 2ae1932..d43bd36 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -47,6 +47,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.io.Closeable;
 import java.util.Arrays;
 
 /**
@@ -54,7 +55,7 @@
  *
  * {@hide}
  */
-final class StringBlock {
+public final class StringBlock implements Closeable {
     private static final String TAG = "AssetManager";
     private static final boolean localLOGV = false;
 
@@ -175,6 +176,7 @@
         }
     }
 
+    @Override
     public void close() {
         synchronized (this) {
             if (mOpen) {
@@ -517,7 +519,7 @@
      *  of this newly creating StringBlock.
      */
     @UnsupportedAppUsage
-    StringBlock(long obj, boolean useSparse) {
+    public StringBlock(long obj, boolean useSparse) {
         mNative = obj;
         mUseSparse = useSparse;
         mOwnsNative = false;
diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java
index 06cafdb..968ab40 100644
--- a/core/java/android/content/res/ThemedResourceCache.java
+++ b/core/java/android/content/res/ThemedResourceCache.java
@@ -22,8 +22,8 @@
 import android.content.pm.ActivityInfo.Config;
 import android.content.res.Resources.Theme;
 import android.content.res.Resources.ThemeKey;
-import android.util.LongSparseArray;
 import android.util.ArrayMap;
+import android.util.LongSparseArray;
 
 import java.lang.ref.WeakReference;
 
@@ -234,4 +234,18 @@
         return entry == null || (configChanges != 0
                 && shouldInvalidateEntry(entry, configChanges));
     }
+
+    public synchronized void clear() {
+        if (mThemedEntries != null) {
+            mThemedEntries.clear();
+        }
+
+        if (mUnthemedEntries != null) {
+            mUnthemedEntries.clear();
+        }
+
+        if (mNullThemedEntries != null) {
+            mNullThemedEntries.clear();
+        }
+    }
 }
diff --git a/core/java/android/content/res/loader/DirectoryResourceLoader.java b/core/java/android/content/res/loader/DirectoryResourceLoader.java
new file mode 100644
index 0000000..7d90e72
--- /dev/null
+++ b/core/java/android/content/res/loader/DirectoryResourceLoader.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 android.content.res.loader;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A {@link ResourceLoader} that searches a directory for assets.
+ *
+ * Assumes that resource paths are resolvable child paths of the directory passed in.
+ */
+public class DirectoryResourceLoader implements ResourceLoader {
+
+    @NonNull
+    private final File mDirectory;
+
+    public DirectoryResourceLoader(@NonNull File directory) {
+        this.mDirectory = directory;
+    }
+
+    @Nullable
+    @Override
+    public InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
+        File file = findFile(path);
+        if (file == null || !file.exists()) {
+            return null;
+        }
+        return new FileInputStream(file);
+    }
+
+    @Nullable
+    @Override
+    public ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException {
+        File file = findFile(path);
+        if (file == null || !file.exists()) {
+            return null;
+        }
+        return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+    }
+
+    /**
+     * Find the file for the given path encoded into the resource table.
+     */
+    @Nullable
+    public File findFile(@NonNull String path) {
+        return mDirectory.toPath().resolve(path).toFile();
+    }
+
+    @NonNull
+    public File getDirectory() {
+        return mDirectory;
+    }
+}
diff --git a/core/java/android/content/res/loader/ResourceLoader.java b/core/java/android/content/res/loader/ResourceLoader.java
new file mode 100644
index 0000000..af32aa2
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourceLoader.java
@@ -0,0 +1,116 @@
+/*
+ * 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.content.res.loader;
+
+import android.annotation.AnyRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.ParcelFileDescriptor;
+import android.util.TypedValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Exposes methods for overriding file-based resource loading from a {@link Resources}.
+ *
+ * To be used with {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)} and related
+ * methods to override resource loading.
+ *
+ * Note that this class doesn't actually contain any resource data. Non-file-based resources are
+ * loaded directly from the {@link ResourcesProvider}'s .arsc representation.
+ *
+ * An instance's methods will only be called if its corresponding {@link ResourcesProvider}'s
+ * resources table contains an entry for the resource ID being resolved,
+ * with the exception of the non-cookie variants of {@link AssetManager}'s openAsset and
+ * openNonAsset.
+ *
+ * Those methods search backwards through all {@link ResourceLoader}s and then any paths provided
+ * by the application or system.
+ *
+ * Otherwise, an ARSC that defines R.drawable.some_id must be provided if a {@link ResourceLoader}
+ * wants to point R.drawable.some_id to a different file on disk.
+ */
+public interface ResourceLoader {
+
+    /**
+     * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
+     * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return a
+     * {@link Drawable} which should be returned by the parent
+     * {@link Resources#getDrawable(int, Resources.Theme)}.
+     *
+     * @param value   the resolved {@link TypedValue} before it has been converted to a Drawable
+     *                object
+     * @param id      the R.drawable ID this resolution is for
+     * @param density the requested density
+     * @param theme   the {@link Resources.Theme} resolved under
+     * @return null if resolution should try to find an entry inside the {@link ResourcesProvider},
+     * including calling through to {@link #loadAsset(String, int)} or {@link #loadAssetFd(String)}
+     */
+    @Nullable
+    default Drawable loadDrawable(@NonNull TypedValue value, int id, int density,
+            @Nullable Resources.Theme theme) {
+        return null;
+    }
+
+    /**
+     * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
+     * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an
+     * {@link XmlResourceParser} which should be returned by the parent
+     * {@link Resources#getDrawable(int, Resources.Theme)}.
+     *
+     * @param path the string that was found in the string pool
+     * @param id   the XML ID this resolution is for, can be R.anim, R.layout, or R.xml
+     * @return null if resolution should try to find an entry inside the {@link ResourcesProvider},
+     * including calling through to {@link #loadAssetFd(String)} (String, int)}
+     */
+    @Nullable
+    default XmlResourceParser loadXmlResourceParser(@NonNull String path, @AnyRes int id) {
+        return null;
+    }
+
+    /**
+     * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
+     * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an
+     * {@link InputStream} which should be returned when an asset is loaded by {@link AssetManager}.
+     * Assets will be loaded from a provider's root, with anything in its assets subpath prefixed
+     * with "assets/".
+     *
+     * @param path       the asset path to load
+     * @param accessMode {@link AssetManager} access mode; does not have to be respected
+     * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}
+     */
+    @Nullable
+    default InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
+        return null;
+    }
+
+    /**
+     * {@link ParcelFileDescriptor} variant of {@link #loadAsset(String, int)}.
+     *
+     * @param path the asset path to load
+     * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}
+     */
+    @Nullable
+    default ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException {
+        return null;
+    }
+}
diff --git a/core/java/android/content/res/loader/ResourceLoaderManager.java b/core/java/android/content/res/loader/ResourceLoaderManager.java
new file mode 100644
index 0000000..ddbfa81
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourceLoaderManager.java
@@ -0,0 +1,189 @@
+/*
+ * 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.content.res.loader;
+
+import android.annotation.Nullable;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.ResourcesImpl;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public class ResourceLoaderManager {
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final List<Pair<ResourceLoader, ResourcesProvider>> mResourceLoaders =
+            new ArrayList<>();
+
+    @GuardedBy("mLock")
+    private ResourcesImpl mResourcesImpl;
+
+    public ResourceLoaderManager(ResourcesImpl resourcesImpl) {
+        this.mResourcesImpl = resourcesImpl;
+        this.mResourcesImpl.getAssets().setResourceLoaderManager(this);
+    }
+
+    /**
+     * Copies the list to ensure that ongoing mutations don't affect the list if it's being used
+     * as a search set.
+     *
+     * @see Resources#getLoaders()
+     */
+    public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() {
+        synchronized (mLock) {
+            return new ArrayList<>(mResourceLoaders);
+        }
+    }
+
+    /**
+     * Returns a list for searching for a loader. Locks and copies the list to ensure that
+     * ongoing mutations don't affect the search set.
+     */
+    public List<Pair<ResourceLoader, ResourcesProvider>> getInternalList() {
+        synchronized (mLock) {
+            return new ArrayList<>(mResourceLoaders);
+        }
+    }
+
+    /**
+     * TODO(b/136251855): Consider optional boolean ignoreConfigurations to allow ResourceLoader
+     * to override every configuration in the target package
+     *
+     * @see Resources#addLoader(ResourceLoader, ResourcesProvider)
+     */
+    public void addLoader(ResourceLoader resourceLoader, ResourcesProvider resourcesProvider,
+            int index) {
+        synchronized (mLock) {
+            for (int listIndex = 0; listIndex < mResourceLoaders.size(); listIndex++) {
+                if (Objects.equals(mResourceLoaders.get(listIndex).first, resourceLoader)) {
+                    throw new IllegalArgumentException("Cannot add the same ResourceLoader twice");
+                }
+            }
+
+            mResourceLoaders.add(index, Pair.create(resourceLoader, resourcesProvider));
+            updateLoaders();
+        }
+    }
+
+    /**
+     * @see Resources#removeLoader(ResourceLoader)
+     */
+    public int removeLoader(ResourceLoader resourceLoader) {
+        synchronized (mLock) {
+            int indexOfLoader = -1;
+
+            for (int index = 0; index < mResourceLoaders.size(); index++) {
+                if (mResourceLoaders.get(index).first == resourceLoader) {
+                    indexOfLoader = index;
+                    break;
+                }
+            }
+
+            if (indexOfLoader < 0) {
+                return indexOfLoader;
+            }
+
+            mResourceLoaders.remove(indexOfLoader);
+            updateLoaders();
+            return indexOfLoader;
+        }
+    }
+
+    /**
+     * @see Resources#setLoaders(List)
+     */
+    public void setLoaders(
+            @Nullable List<Pair<ResourceLoader, ResourcesProvider>> newLoadersAndProviders) {
+        synchronized (mLock) {
+            if (ArrayUtils.isEmpty(newLoadersAndProviders)) {
+                mResourceLoaders.clear();
+                updateLoaders();
+                return;
+            }
+
+            int size = newLoadersAndProviders.size();
+            for (int newIndex = 0; newIndex < size; newIndex++) {
+                ResourceLoader resourceLoader = newLoadersAndProviders.get(newIndex).first;
+                for (int oldIndex = 0; oldIndex < mResourceLoaders.size(); oldIndex++) {
+                    if (Objects.equals(mResourceLoaders.get(oldIndex).first, resourceLoader)) {
+                        throw new IllegalArgumentException(
+                                "Cannot add the same ResourceLoader twice");
+                    }
+                }
+            }
+
+            mResourceLoaders.clear();
+            mResourceLoaders.addAll(newLoadersAndProviders);
+
+            updateLoaders();
+        }
+    }
+
+    /**
+     * Swap the tracked {@link ResourcesImpl} and reattach any loaders to it.
+     */
+    public void onImplUpdate(ResourcesImpl resourcesImpl) {
+        synchronized (mLock) {
+            this.mResourcesImpl = resourcesImpl;
+            updateLoaders();
+        }
+    }
+
+    private void updateLoaders() {
+        synchronized (mLock) {
+            AssetManager assetManager = mResourcesImpl.getAssets();
+            ApkAssets[] existingApkAssets = assetManager.getApkAssets();
+            int baseApkAssetsSize = 0;
+            for (int index = existingApkAssets.length - 1; index >= 0; index--) {
+                // Loaders are always last, so the first non-loader is the end of the base assets
+                if (!existingApkAssets[index].isForLoader()) {
+                    baseApkAssetsSize = index + 1;
+                    break;
+                }
+            }
+
+            List<ApkAssets> newAssets = new ArrayList<>();
+            for (int index = 0; index < baseApkAssetsSize; index++) {
+                newAssets.add(existingApkAssets[index]);
+            }
+
+            int size = mResourceLoaders.size();
+            for (int index = 0; index < size; index++) {
+                ApkAssets apkAssets = mResourceLoaders.get(index).second.getApkAssets();
+                newAssets.add(apkAssets);
+            }
+
+            assetManager.setApkAssets(newAssets.toArray(new ApkAssets[0]), true);
+
+            // Short of resolving every resource, it's too difficult to determine what has changed
+            // when a resource loader is changed, so just clear everything.
+            mResourcesImpl.clearAllCaches();
+        }
+    }
+}
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
new file mode 100644
index 0000000..050aeb7
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -0,0 +1,139 @@
+/*
+ * 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.content.res.loader;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.ApkAssets;
+import android.content.res.Resources;
+import android.os.ParcelFileDescriptor;
+import android.os.SharedMemory;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Provides methods to load resources from an .apk or .arsc file to pass to
+ * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}.
+ *
+ * It is the responsibility of the app to close any instances.
+ */
+public final class ResourcesProvider implements AutoCloseable, Closeable {
+
+    /**
+     * Contains no data, assuming that any resource loading behavior will be handled in the
+     * corresponding {@link ResourceLoader}.
+     */
+    @NonNull
+    public static ResourcesProvider empty() {
+        return new ResourcesProvider(ApkAssets.loadEmptyForLoader());
+    }
+
+    /**
+     * Read from an .apk file descriptor.
+     *
+     * The file descriptor is duplicated and the one passed in may be closed by the application
+     * at any time.
+     */
+    @NonNull
+    public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor)
+            throws IOException {
+        return new ResourcesProvider(
+                ApkAssets.loadApkForLoader(fileDescriptor.getFileDescriptor()));
+    }
+
+    /**
+     * Read from an .apk file representation in memory.
+     */
+    @NonNull
+    public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory)
+            throws IOException {
+        return new ResourcesProvider(
+                ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()));
+    }
+
+    /**
+     * Read from an .arsc file descriptor.
+     *
+     * The file descriptor is duplicated and the one passed in may be closed by the application
+     * at any time.
+     */
+    @NonNull
+    public static ResourcesProvider loadFromArsc(@NonNull ParcelFileDescriptor fileDescriptor)
+            throws IOException {
+        return new ResourcesProvider(
+                ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()));
+    }
+
+    /**
+     * Read from an .arsc file representation in memory.
+     */
+    @NonNull
+    public static ResourcesProvider loadFromArsc(@NonNull SharedMemory sharedMemory)
+            throws IOException {
+        return new ResourcesProvider(
+                ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor()));
+    }
+
+    /**
+     * Read from a split installed alongside the application, which may not have been
+     * loaded initially because the application requested isolated split loading.
+     */
+    @NonNull
+    public static ResourcesProvider loadFromSplit(@NonNull Context context,
+            @NonNull String splitName) throws IOException {
+        ApplicationInfo appInfo = context.getApplicationInfo();
+        int splitIndex = ArrayUtils.indexOf(appInfo.splitNames, splitName);
+        if (splitIndex < 0) {
+            throw new IllegalArgumentException("Split " + splitName + " not found");
+        }
+
+        String splitPath = appInfo.getSplitCodePaths()[splitIndex];
+        return new ResourcesProvider(ApkAssets.loadApkForLoader(splitPath));
+    }
+
+
+    @NonNull
+    private final ApkAssets mApkAssets;
+
+    private ResourcesProvider(@NonNull ApkAssets apkAssets) {
+        this.mApkAssets = apkAssets;
+    }
+
+    /** @hide */
+    @NonNull
+    public ApkAssets getApkAssets() {
+        return mApkAssets;
+    }
+
+    @Override
+    public void close() {
+        try {
+            mApkAssets.close();
+        } catch (Throwable ignored) {
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        close();
+        super.finalize();
+    }
+}
diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl
index 106b7be..fe9141c 100644
--- a/core/java/android/net/INetworkPolicyListener.aidl
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -15,6 +15,7 @@
  */
 
 package android.net;
+import android.telephony.SubscriptionPlan;
 
 /** {@hide} */
 oneway interface INetworkPolicyListener {
@@ -22,5 +23,6 @@
     void onMeteredIfacesChanged(in String[] meteredIfaces);
     void onRestrictBackgroundChanged(boolean restrictBackground);
     void onUidPoliciesChanged(int uid, int uidPolicies);
-    void onSubscriptionOverride(int subId, int overrideMask, int overrideValue, long networkTypeMask);
+    void onSubscriptionOverride(int subId, int overrideMask, int overrideValue);
+    void onSubscriptionPlansChanged(int subId, in SubscriptionPlan[] plans);
 }
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 90327663..385cb1d 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -76,7 +76,7 @@
     SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage);
     void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage);
     String getSubscriptionPlansOwner(int subId);
-    void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long networkTypeMask, long timeoutMillis, String callingPackage);
+    void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long timeoutMillis, String callingPackage);
 
     void factoryReset(String subscriber);
 
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 628dcd2..9150aae 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -31,6 +31,7 @@
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.telephony.SubscriptionPlan;
 import android.util.DebugUtils;
 import android.util.Pair;
 import android.util.Range;
@@ -380,7 +381,8 @@
         @Override public void onMeteredIfacesChanged(String[] meteredIfaces) { }
         @Override public void onRestrictBackgroundChanged(boolean restrictBackground) { }
         @Override public void onUidPoliciesChanged(int uid, int uidPolicies) { }
-        @Override public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue,
-                long networkTypeMask) { }
+        @Override public void onSubscriptionOverride(int subId, int overrideMask,
+                int overrideValue) { }
+        @Override public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) { }
     }
 }
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 2c9333b..ef3afab 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -1085,4 +1085,13 @@
         StrictMode.clearGatheredViolations();
         return res;
     }
+
+    /**
+     * Returns the specified service from servicemanager. If the service is not running,
+     * servicemanager will attempt to start it, and this function will wait for it to be ready.
+     * Returns nullptr only if there are permission problems or fatal errors.
+     * @hide
+     */
+    public static final native @Nullable IBinder waitForService(@NonNull String serviceName)
+            throws RemoteException;
 }
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index bcb94ce..fdb44e7 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -35,12 +35,15 @@
 import android.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.net.Uri;
 import android.os.MessageQueue.OnFileDescriptorEventListener;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
 import android.system.StructStat;
 import android.util.Log;
+import android.util.Size;
 
 import dalvik.system.CloseGuard;
 import dalvik.system.VMRuntime;
@@ -204,6 +207,10 @@
 
     /**
      * Create a new ParcelFileDescriptor accessing a given file.
+     * <p>
+     * This method should only be used for files that you have direct access to;
+     * if you'd like to work with files hosted outside your app, use an API like
+     * {@link ContentResolver#openFile(Uri, String, CancellationSignal)}.
      *
      * @param file The file to be opened.
      * @param mode The desired access mode, must be one of
@@ -226,6 +233,10 @@
 
     /**
      * Create a new ParcelFileDescriptor accessing a given file.
+     * <p>
+     * This method should only be used for files that you have direct access to;
+     * if you'd like to work with files hosted outside your app, use an API like
+     * {@link ContentResolver#openFile(Uri, String, CancellationSignal)}.
      *
      * @param file The file to be opened.
      * @param mode The desired access mode, must be one of
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 654b0f7..26c1ec1 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -31,8 +31,8 @@
     void revokeRuntimePermissions(in Bundle request, boolean doDryRun, int reason,
             String callerPackageName, in AndroidFuture callback);
     void getRuntimePermissionBackup(in UserHandle user, in ParcelFileDescriptor pipe);
-    void restoreRuntimePermissionBackup(in UserHandle user, in ParcelFileDescriptor pipe);
-    void restoreDelayedRuntimePermissionBackup(String packageName, in UserHandle user,
+    void stageAndApplyRuntimePermissionsBackup(in UserHandle user, in ParcelFileDescriptor pipe);
+    void applyStagedRuntimePermissionBackup(String packageName, in UserHandle user,
             in AndroidFuture callback);
     void getAppPermissions(String packageName, in AndroidFuture callback);
     void revokeRuntimePermission(String packageName, String permissionName);
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 923d9f8..421e29e 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -62,6 +62,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -139,20 +140,6 @@
     }
 
     /**
-     * Callback for delivering the result of {@link #getRuntimePermissionBackup}.
-     *
-     * @hide
-     */
-    public interface OnGetRuntimePermissionBackupCallback {
-        /**
-         * The result for {@link #getRuntimePermissionBackup}.
-         *
-         * @param backup The backup file
-         */
-        void onGetRuntimePermissionsBackup(@NonNull byte[] backup);
-    }
-
-    /**
      * Callback for delivering the result of {@link #getAppPermissions}.
      *
      * @hide
@@ -246,6 +233,24 @@
     }
 
     /**
+     * Throw a {@link SecurityException} if not at least one of the permissions is granted.
+     *
+     * @param requiredPermissions A list of permissions. Any of of them if sufficient to pass the
+     *                            check
+     */
+    private void enforceSomePermissionsGrantedToSelf(@NonNull String... requiredPermissions) {
+        for (String requiredPermission : requiredPermissions) {
+            if (mContext.checkSelfPermission(requiredPermission)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+        }
+
+        throw new SecurityException("At lest one of the following permissions is required: "
+                + Arrays.toString(requiredPermissions));
+    }
+
+    /**
      * Revoke a set of runtime permissions for various apps.
      *
      * @param request The permissions to revoke as {@code Map<packageName, List<permission>>}
@@ -268,11 +273,7 @@
         }
 
         // Check required permission to fail immediately instead of inside the oneway binder call
-        if (mContext.checkSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
-                    + " required");
-        }
+        enforceSomePermissionsGrantedToSelf(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
 
         mRemoteService.postAsync(service -> {
             Bundle bundledizedRequest = new Bundle();
@@ -358,46 +359,61 @@
      *
      * @param user The user to be backed up
      * @param executor Executor on which to invoke the callback
-     * @param callback Callback to receive the result
-     *
-     * @hide
+     * @param callback Callback to receive the result. The resulting backup-file is opaque and no
+     *                 guarantees are made other than that the file can be send to
+     *                 {@link #restoreRuntimePermissionBackup} in this and future versions of
+     *                 Android.
      */
     @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
     public void getRuntimePermissionBackup(@NonNull UserHandle user,
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnGetRuntimePermissionBackupCallback callback) {
+            @NonNull Consumer<byte[]> callback) {
         checkNotNull(user);
         checkNotNull(executor);
         checkNotNull(callback);
 
+        // Check required permission to fail immediately instead of inside the oneway binder call
+        enforceSomePermissionsGrantedToSelf(Manifest.permission.GET_RUNTIME_PERMISSIONS);
+
         mRemoteService.postAsync(service -> RemoteStream.receiveBytes(remotePipe -> {
             service.getRuntimePermissionBackup(user, remotePipe);
         })).whenCompleteAsync((bytes, err) -> {
             if (err != null) {
                 Log.e(TAG, "Error getting permission backup", err);
-                callback.onGetRuntimePermissionsBackup(EmptyArray.BYTE);
+                callback.accept(EmptyArray.BYTE);
             } else {
-                callback.onGetRuntimePermissionsBackup(bytes);
+                callback.accept(bytes);
             }
         }, executor);
     }
 
     /**
-     * Restore a backup of the runtime permissions.
+     * Restore a {@link #getRuntimePermissionBackup backup-file} of the runtime permissions.
      *
-     * @param backup the backup to restore. The backup is sent asynchronously, hence it should not
-     *               be modified after calling this method.
+     * <p>This might leave some part of the backup-file unapplied if an package mentioned in the
+     * backup-file is not yet installed. It is required that
+     * {@link #applyStagedRuntimePermissionBackup} is called after any package is installed to
+     * apply the rest of the backup-file.
+     *
+     * @param backup the backup-file to restore. The backup is sent asynchronously, hence it should
+     *               not be modified after calling this method.
      * @param user The user to be restore
-     *
-     * @hide
      */
-    @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
-    public void restoreRuntimePermissionBackup(@NonNull byte[] backup, @NonNull UserHandle user) {
+    @RequiresPermission(anyOf = {
+            Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+            Manifest.permission.RESTORE_RUNTIME_PERMISSIONS
+    })
+    public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[] backup,
+            @NonNull UserHandle user) {
         checkNotNull(backup);
         checkNotNull(user);
 
+        // Check required permission to fail immediately instead of inside the oneway binder call
+        enforceSomePermissionsGrantedToSelf(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+                Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
+
         mRemoteService.postAsync(service -> RemoteStream.sendBytes(remotePipe -> {
-            service.restoreRuntimePermissionBackup(user, remotePipe);
+            service.stageAndApplyRuntimePermissionsBackup(user, remotePipe);
         }, backup))
                 .whenComplete((nullResult, err) -> {
                     if (err != null) {
@@ -407,17 +423,22 @@
     }
 
     /**
-     * Restore a backup of the runtime permissions that has been delayed.
+     * Restore unapplied parts of a {@link #stageAndApplyRuntimePermissionsBackup previously staged}
+     * backup-file of the runtime permissions.
+     *
+     * <p>This should be called every time after a package is installed until the callback
+     * reports that there is no more unapplied backup left.
      *
      * @param packageName The package that is ready to have it's permissions restored.
-     * @param user The user to restore
+     * @param user The user the package belongs to
      * @param executor Executor to execute the callback on
-     * @param callback Is called with {@code true} iff there is still more delayed backup left
-     *
-     * @hide
+     * @param callback Is called with {@code true} iff there is still more unapplied backup left
      */
-    @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
-    public void restoreDelayedRuntimePermissionBackup(@NonNull String packageName,
+    @RequiresPermission(anyOf = {
+            Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+            Manifest.permission.RESTORE_RUNTIME_PERMISSIONS
+    })
+    public void applyStagedRuntimePermissionBackup(@NonNull String packageName,
             @NonNull UserHandle user,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<Boolean> callback) {
@@ -426,13 +447,17 @@
         checkNotNull(executor);
         checkNotNull(callback);
 
+        // Check required permission to fail immediately instead of inside the oneway binder call
+        enforceSomePermissionsGrantedToSelf(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+                Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
+
         mRemoteService.postAsync(service -> {
-            AndroidFuture<Boolean> restoreDelayedRuntimePermissionBackupResult =
+            AndroidFuture<Boolean> applyStagedRuntimePermissionBackupResult =
                     new AndroidFuture<>();
-            service.restoreDelayedRuntimePermissionBackup(packageName, user,
-                    restoreDelayedRuntimePermissionBackupResult);
-            return restoreDelayedRuntimePermissionBackupResult;
-        }).whenCompleteAsync((restoreDelayedRuntimePermissionBackupResult, err) -> {
+            service.applyStagedRuntimePermissionBackup(packageName, user,
+                    applyStagedRuntimePermissionBackupResult);
+            return applyStagedRuntimePermissionBackupResult;
+        }).whenCompleteAsync((applyStagedRuntimePermissionBackupResult, err) -> {
             long token = Binder.clearCallingIdentity();
             try {
                 if (err != null) {
@@ -440,7 +465,7 @@
                     callback.accept(true);
                 } else {
                     callback.accept(
-                            Boolean.TRUE.equals(restoreDelayedRuntimePermissionBackupResult));
+                            Boolean.TRUE.equals(applyStagedRuntimePermissionBackupResult));
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 7363d77..8f765fa 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -54,6 +54,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
@@ -105,31 +106,54 @@
     public abstract void onGetRuntimePermissionsBackup(@NonNull UserHandle user,
             @NonNull OutputStream backup, @NonNull Runnable callback);
 
+
+    /**
+     * @deprecated Implement {@link #onStageAndApplyRuntimePermissionsBackup} instead
+     */
+    @Deprecated
+    @BinderThread
+    public void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user,
+            @NonNull InputStream backup, @NonNull Runnable callback) {
+    }
+
     /**
      * Restore a backup of the runtime permissions.
      *
      * <p>If an app mentioned in the backup is not installed the state should be saved to later
-     * be restored via {@link #onRestoreDelayedRuntimePermissionsBackup}.
+     * be restored via {@link #onApplyStagedRuntimePermissionBackup}.
      *
      * @param user The user to restore
      * @param backup The stream to read the backup from
      * @param callback Callback waiting for operation to be complete
      */
     @BinderThread
-    public abstract void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user,
-            @NonNull InputStream backup, @NonNull Runnable callback);
+    public void onStageAndApplyRuntimePermissionsBackup(@NonNull UserHandle user,
+            @NonNull InputStream backup, @NonNull Runnable callback) {
+        onRestoreRuntimePermissionsBackup(user, backup, callback);
+    }
+
+    /**
+     * @deprecated Implement {@link #onApplyStagedRuntimePermissionBackup} instead
+     */
+    @Deprecated
+    @BinderThread
+    public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName,
+            @NonNull UserHandle user, @NonNull Consumer<Boolean> callback) {
+    }
 
     /**
      * Restore the permission state of an app that was provided in
-     * {@link #onRestoreRuntimePermissionsBackup} but could not be restored back then.
+     * {@link #onStageAndApplyRuntimePermissionsBackup} but could not be restored back then.
      *
      * @param packageName The app to restore
      * @param user The user to restore
      * @param callback Callback waiting for whether there is still delayed backup left
      */
     @BinderThread
-    public abstract void onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName,
-            @NonNull UserHandle user, @NonNull Consumer<Boolean> callback);
+    public void onApplyStagedRuntimePermissionBackup(@NonNull String packageName,
+            @NonNull UserHandle user, @NonNull Consumer<Boolean> callback) {
+        onRestoreDelayedRuntimePermissionsBackup(packageName, user, callback);
+    }
 
     /**
      * Gets the runtime permissions for an app.
@@ -238,7 +262,8 @@
                     request.put(packageName, permissions);
                 }
 
-                enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, null);
+                enforceSomePermissionsGrantedToCaller(
+                        Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
 
                 // Verify callerPackageName
                 try {
@@ -258,12 +283,33 @@
                         });
             }
 
+            /**
+             * Throw a {@link SecurityException} if not at least one of the permissions is granted.
+             *
+             * @param requiredPermissions A list of permissions. Any of of them if sufficient to
+             *                            pass the check
+             */
+            private void enforceSomePermissionsGrantedToCaller(
+                    @NonNull String... requiredPermissions) {
+                for (String requiredPermission : requiredPermissions) {
+                    if (checkCallingPermission(requiredPermission)
+                            == PackageManager.PERMISSION_GRANTED) {
+                        return;
+                    }
+                }
+
+                throw new SecurityException(
+                        "At lest one of the following permissions is required: " + Arrays.toString(
+                                requiredPermissions));
+            }
+
+
             @Override
             public void getRuntimePermissionBackup(UserHandle user, ParcelFileDescriptor pipe) {
                 checkNotNull(user);
                 checkNotNull(pipe);
 
-                enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+                enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
 
                 try (OutputStream backup = new ParcelFileDescriptor.AutoCloseOutputStream(pipe)) {
                     CountDownLatch latch = new CountDownLatch(1);
@@ -277,15 +323,17 @@
             }
 
             @Override
-            public void restoreRuntimePermissionBackup(UserHandle user, ParcelFileDescriptor pipe) {
+            public void stageAndApplyRuntimePermissionsBackup(UserHandle user,
+                    ParcelFileDescriptor pipe) {
                 checkNotNull(user);
                 checkNotNull(pipe);
 
-                enforceCallingPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS, null);
+                enforceSomePermissionsGrantedToCaller(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+                        Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
 
                 try (InputStream backup = new ParcelFileDescriptor.AutoCloseInputStream(pipe)) {
                     CountDownLatch latch = new CountDownLatch(1);
-                    onRestoreRuntimePermissionsBackup(user, backup, latch::countDown);
+                    onStageAndApplyRuntimePermissionsBackup(user, backup, latch::countDown);
                     latch.await();
                 } catch (IOException e) {
                     Log.e(LOG_TAG, "Could not open pipe to read backup from", e);
@@ -295,15 +343,16 @@
             }
 
             @Override
-            public void restoreDelayedRuntimePermissionBackup(String packageName, UserHandle user,
+            public void applyStagedRuntimePermissionBackup(String packageName, UserHandle user,
                     AndroidFuture callback) {
                 checkNotNull(packageName);
                 checkNotNull(user);
                 checkNotNull(callback);
 
-                enforceCallingPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS, null);
+                enforceSomePermissionsGrantedToCaller(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+                        Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
 
-                onRestoreDelayedRuntimePermissionsBackup(packageName, user, callback::complete);
+                onApplyStagedRuntimePermissionBackup(packageName, user, callback::complete);
             }
 
             @Override
@@ -311,7 +360,7 @@
                 checkNotNull(packageName, "packageName");
                 checkNotNull(callback, "callback");
 
-                enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+                enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
 
                 onGetAppPermissions(packageName, callback::complete);
             }
@@ -321,7 +370,8 @@
                 checkNotNull(packageName, "packageName");
                 checkNotNull(permissionName, "permissionName");
 
-                enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, null);
+                enforceSomePermissionsGrantedToCaller(
+                        Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
 
                 CountDownLatch latch = new CountDownLatch(1);
                 PermissionControllerService.this.onRevokeRuntimePermission(packageName,
@@ -340,7 +390,7 @@
                 checkFlagsArgument(flags, COUNT_WHEN_SYSTEM | COUNT_ONLY_WHEN_GRANTED);
                 checkNotNull(callback, "callback");
 
-                enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+                enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
 
                 onCountPermissionApps(permissionNames, flags, callback::complete);
             }
@@ -351,7 +401,7 @@
                 checkArgumentNonnegative(numMillis);
                 checkNotNull(callback, "callback");
 
-                enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+                enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
 
                 onGetPermissionUsages(countSystem, numMillis, callback::complete);
             }
@@ -369,15 +419,17 @@
                 checkNotNull(callback);
 
                 if (grantState == PERMISSION_GRANT_STATE_DENIED) {
-                    enforceCallingPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS, null);
+                    enforceSomePermissionsGrantedToCaller(
+                            Manifest.permission.GRANT_RUNTIME_PERMISSIONS);
                 }
 
                 if (grantState == PERMISSION_GRANT_STATE_DENIED) {
-                    enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, null);
+                    enforceSomePermissionsGrantedToCaller(
+                            Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
                 }
 
-                enforceCallingPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
-                        null);
+                enforceSomePermissionsGrantedToCaller(
+                        Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
 
                 onSetRuntimePermissionGrantStateByDeviceAdmin(callerPackageName,
                         packageName, permission, grantState, callback::complete);
@@ -387,8 +439,8 @@
             public void grantOrUpgradeDefaultRuntimePermissions(@NonNull AndroidFuture callback) {
                 checkNotNull(callback, "callback");
 
-                enforceCallingPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
-                        null);
+                enforceSomePermissionsGrantedToCaller(
+                        Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
 
                 onGrantOrUpgradeDefaultRuntimePermissions(() -> callback.complete(true));
             }
diff --git a/core/java/android/print/TEST_MAPPING b/core/java/android/print/TEST_MAPPING
new file mode 100644
index 0000000..4fa8822
--- /dev/null
+++ b/core/java/android/print/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsPrintTestCases",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/java/android/print/pdf/TEST_MAPPING b/core/java/android/print/pdf/TEST_MAPPING
new file mode 100644
index 0000000..d763598
--- /dev/null
+++ b/core/java/android/print/pdf/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsPdfTestCases"
+    }
+  ]
+}
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index fd81178..eb09930 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -363,15 +363,22 @@
          * <p>
          * Type: INTEGER (int)
          *
-         * @see #FLAG_SUPPORTS_WRITE
-         * @see #FLAG_SUPPORTS_DELETE
-         * @see #FLAG_SUPPORTS_THUMBNAIL
+         * @see #FLAG_DIR_BLOCKS_TREE
          * @see #FLAG_DIR_PREFERS_GRID
          * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
-         * @see #FLAG_VIRTUAL_DOCUMENT
+         * @see #FLAG_DIR_SUPPORTS_CREATE
+         * @see #FLAG_PARTIAL
          * @see #FLAG_SUPPORTS_COPY
+         * @see #FLAG_SUPPORTS_DELETE
+         * @see #FLAG_SUPPORTS_METADATA
          * @see #FLAG_SUPPORTS_MOVE
          * @see #FLAG_SUPPORTS_REMOVE
+         * @see #FLAG_SUPPORTS_RENAME
+         * @see #FLAG_SUPPORTS_SETTINGS
+         * @see #FLAG_SUPPORTS_THUMBNAIL
+         * @see #FLAG_SUPPORTS_WRITE
+         * @see #FLAG_VIRTUAL_DOCUMENT
+         * @see #FLAG_WEB_LINKABLE
          */
         public static final String COLUMN_FLAGS = "flags";
 
@@ -542,6 +549,23 @@
          * @see DocumentsContract#getDocumentMetadata(ContentInterface, Uri)
          */
         public static final int FLAG_SUPPORTS_METADATA = 1 << 14;
+
+        /**
+         * Flag indicating that a document is a directory that wants to block itself
+         * from being selected when the user launches an {@link Intent#ACTION_OPEN_DOCUMENT_TREE}
+         * intent. Only valid when {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
+         * <p>
+         * Note that this flag <em>only</em> applies to the single directory to which it is
+         * applied. It does <em>not</em> block the user from selecting either a parent or
+         * child directory during an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} request.
+         * In particular, the only way to guarantee that a specific directory can never
+         * be granted via an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} request is to ensure
+         * that both it and <em>all of its parent directories</em> have set this flag.
+         *
+         * @see Intent#ACTION_OPEN_DOCUMENT_TREE
+         * @see #COLUMN_FLAGS
+         */
+        public static final int FLAG_DIR_BLOCKS_TREE = 1 << 15;
     }
 
     /**
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 079a42d..a1333df 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -39,7 +39,6 @@
 import android.content.Intent;
 import android.content.UriPermission;
 import android.database.Cursor;
-import android.database.DatabaseUtils;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.ImageDecoder;
@@ -47,6 +46,7 @@
 import android.graphics.PostProcessor;
 import android.media.ExifInterface;
 import android.media.MediaFile;
+import android.media.MediaFormat;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -69,15 +69,19 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import libcore.util.HexEncoding;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.Set;
 import java.util.regex.Pattern;
@@ -2058,7 +2062,17 @@
             /**
              * A non human readable key calculated from the TITLE, used for
              * searching, sorting and grouping
+             *
+             * @see Audio#keyFor(String)
+             * @deprecated These keys are generated using
+             *             {@link java.util.Locale#ROOT}, which means they don't
+             *             reflect locale-specific sorting preferences. To apply
+             *             locale-specific sorting preferences, use
+             *             {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+             *             {@code COLLATE LOCALIZED}, or
+             *             {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
              */
+            @Deprecated
             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
             public static final String TITLE_KEY = "title_key";
 
@@ -2103,7 +2117,17 @@
             /**
              * A non human readable key calculated from the ARTIST, used for
              * searching, sorting and grouping
+             *
+             * @see Audio#keyFor(String)
+             * @deprecated These keys are generated using
+             *             {@link java.util.Locale#ROOT}, which means they don't
+             *             reflect locale-specific sorting preferences. To apply
+             *             locale-specific sorting preferences, use
+             *             {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+             *             {@code COLLATE LOCALIZED}, or
+             *             {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
              */
+            @Deprecated
             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
             public static final String ARTIST_KEY = "artist_key";
 
@@ -2128,7 +2152,17 @@
             /**
              * A non human readable key calculated from the ALBUM, used for
              * searching, sorting and grouping
+             *
+             * @see Audio#keyFor(String)
+             * @deprecated These keys are generated using
+             *             {@link java.util.Locale#ROOT}, which means they don't
+             *             reflect locale-specific sorting preferences. To apply
+             *             locale-specific sorting preferences, use
+             *             {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+             *             {@code COLLATE LOCALIZED}, or
+             *             {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
              */
+            @Deprecated
             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
             public static final String ALBUM_KEY = "album_key";
 
@@ -2185,91 +2219,89 @@
             public static final String IS_AUDIOBOOK = "is_audiobook";
 
             /**
-             * The genre of the audio file, if any
-             * Does not exist in the database - only used by the media scanner for inserts.
-             * @hide
+             * The id of the genre the audio file is from, if any
              */
-            @Deprecated
-            // @Column(Cursor.FIELD_TYPE_STRING)
+            @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
+            public static final String GENRE_ID = "genre_id";
+
+            /**
+             * The genre of the audio file, if any.
+             */
+            @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
             public static final String GENRE = "genre";
 
             /**
-             * The resource URI of a localized title, if any
+             * A non human readable key calculated from the GENRE, used for
+             * searching, sorting and grouping
+             *
+             * @see Audio#keyFor(String)
+             * @deprecated These keys are generated using
+             *             {@link java.util.Locale#ROOT}, which means they don't
+             *             reflect locale-specific sorting preferences. To apply
+             *             locale-specific sorting preferences, use
+             *             {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+             *             {@code COLLATE LOCALIZED}, or
+             *             {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
+             */
+            @Deprecated
+            @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
+            public static final String GENRE_KEY = "genre_key";
+
+            /**
+             * The resource URI of a localized title, if any.
+             * <p>
              * Conforms to this pattern:
-             *   Scheme: {@link ContentResolver.SCHEME_ANDROID_RESOURCE}
-             *   Authority: Package Name of ringtone title provider
-             *   First Path Segment: Type of resource (must be "string")
-             *   Second Path Segment: Resource ID of title
-             * @hide
+             * <ul>
+             * <li>Scheme: {@link ContentResolver#SCHEME_ANDROID_RESOURCE}
+             * <li>Authority: Package Name of ringtone title provider
+             * <li>First Path Segment: Type of resource (must be "string")
+             * <li>Second Path Segment: Resource ID of title
+             * </ul>
              */
             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
             public static final String TITLE_RESOURCE_URI = "title_resource_uri";
         }
 
+        private static final Pattern PATTERN_TRIM_BEFORE = Pattern.compile(
+                "(?i)(^(the|an|a) |,\\s*(the|an|a)$|[^\\w\\s]|^\\s+|\\s+$)");
+        private static final Pattern PATTERN_TRIM_AFTER = Pattern.compile(
+                "(^(00)+|(00)+$)");
+
         /**
-         * Converts a name to a "key" that can be used for grouping, sorting
-         * and searching.
-         * The rules that govern this conversion are:
-         * - remove 'special' characters like ()[]'!?.,
-         * - remove leading/trailing spaces
-         * - convert everything to lowercase
-         * - remove leading "the ", "an " and "a "
-         * - remove trailing ", the|an|a"
-         * - remove accents. This step leaves us with CollationKey data,
-         *   which is not human readable
+         * Converts a user-visible string into a "key" that can be used for
+         * grouping, sorting, and searching.
          *
-         * @param name The artist or album name to convert
-         * @return The "key" for the given name.
+         * @return Opaque token that should not be parsed or displayed to users.
+         * @deprecated These keys are generated using
+         *             {@link java.util.Locale#ROOT}, which means they don't
+         *             reflect locale-specific sorting preferences. To apply
+         *             locale-specific sorting preferences, use
+         *             {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+         *             {@code COLLATE LOCALIZED}, or
+         *             {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
          */
-        public static String keyFor(String name) {
-            if (name != null)  {
-                boolean sortfirst = false;
-                if (name.equals(UNKNOWN_STRING)) {
-                    return "\001";
-                }
-                // Check if the first character is \001. We use this to
-                // force sorting of certain special files, like the silent ringtone.
-                if (name.startsWith("\001")) {
-                    sortfirst = true;
-                }
-                name = name.trim().toLowerCase();
-                if (name.startsWith("the ")) {
-                    name = name.substring(4);
-                }
-                if (name.startsWith("an ")) {
-                    name = name.substring(3);
-                }
-                if (name.startsWith("a ")) {
-                    name = name.substring(2);
-                }
-                if (name.endsWith(", the") || name.endsWith(",the") ||
-                    name.endsWith(", an") || name.endsWith(",an") ||
-                    name.endsWith(", a") || name.endsWith(",a")) {
-                    name = name.substring(0, name.lastIndexOf(','));
-                }
-                name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
-                if (name.length() > 0) {
-                    // Insert a separator between the characters to avoid
-                    // matches on a partial character. If we ever change
-                    // to start-of-word-only matches, this can be removed.
-                    StringBuilder b = new StringBuilder();
-                    b.append('.');
-                    int nl = name.length();
-                    for (int i = 0; i < nl; i++) {
-                        b.append(name.charAt(i));
-                        b.append('.');
-                    }
-                    name = b.toString();
-                    String key = DatabaseUtils.getCollationKey(name);
-                    if (sortfirst) {
-                        key = "\001" + key;
-                    }
-                    return key;
-               } else {
-                    return "";
-                }
+        @Deprecated
+        public static @Nullable String keyFor(@Nullable String name) {
+            if (TextUtils.isEmpty(name)) return null;
+
+            if (UNKNOWN_STRING.equals(name)) {
+                return "01";
             }
-            return null;
+
+            final boolean sortFirst = name.startsWith("\001");
+
+            name = PATTERN_TRIM_BEFORE.matcher(name).replaceAll("");
+            if (TextUtils.isEmpty(name)) return null;
+
+            final Collator c = Collator.getInstance(Locale.ROOT);
+            c.setStrength(Collator.PRIMARY);
+            name = HexEncoding.encodeToString(c.getCollationKey(name).toByteArray(), false);
+
+            name = PATTERN_TRIM_AFTER.matcher(name).replaceAll("");
+            if (sortFirst) {
+                name = "01" + name;
+            }
+            return name;
         }
 
         public static final class Media implements AudioColumns {
@@ -2631,7 +2663,17 @@
             /**
              * A non human readable key calculated from the ARTIST, used for
              * searching, sorting and grouping
+             *
+             * @see Audio#keyFor(String)
+             * @deprecated These keys are generated using
+             *             {@link java.util.Locale#ROOT}, which means they don't
+             *             reflect locale-specific sorting preferences. To apply
+             *             locale-specific sorting preferences, use
+             *             {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+             *             {@code COLLATE LOCALIZED}, or
+             *             {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
              */
+            @Deprecated
             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
             public static final String ARTIST_KEY = "artist_key";
 
@@ -2735,6 +2777,23 @@
             public static final String ARTIST = "artist";
 
             /**
+             * A non human readable key calculated from the ARTIST, used for
+             * searching, sorting and grouping
+             *
+             * @see Audio#keyFor(String)
+             * @deprecated These keys are generated using
+             *             {@link java.util.Locale#ROOT}, which means they don't
+             *             reflect locale-specific sorting preferences. To apply
+             *             locale-specific sorting preferences, use
+             *             {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+             *             {@code COLLATE LOCALIZED}, or
+             *             {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
+             */
+            @Deprecated
+            @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
+            public static final String ARTIST_KEY = "artist_key";
+
+            /**
              * The number of songs on this album
              */
             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
@@ -2769,7 +2828,17 @@
             /**
              * A non human readable key calculated from the ALBUM, used for
              * searching, sorting and grouping
+             *
+             * @see Audio#keyFor(String)
+             * @deprecated These keys are generated using
+             *             {@link java.util.Locale#ROOT}, which means they don't
+             *             reflect locale-specific sorting preferences. To apply
+             *             locale-specific sorting preferences, use
+             *             {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+             *             {@code COLLATE LOCALIZED}, or
+             *             {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
              */
+            @Deprecated
             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
             public static final String ALBUM_KEY = "album_key";
 
@@ -3007,22 +3076,32 @@
             public static final String BOOKMARK = "bookmark";
 
             /**
-             * The standard of color aspects
-             * @hide
+             * The color standard of this media file, if available.
+             *
+             * @see MediaFormat#COLOR_STANDARD_BT709
+             * @see MediaFormat#COLOR_STANDARD_BT601_PAL
+             * @see MediaFormat#COLOR_STANDARD_BT601_NTSC
+             * @see MediaFormat#COLOR_STANDARD_BT2020
              */
             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
             public static final String COLOR_STANDARD = "color_standard";
 
             /**
-             * The transfer of color aspects
-             * @hide
+             * The color transfer of this media file, if available.
+             *
+             * @see MediaFormat#COLOR_TRANSFER_LINEAR
+             * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
+             * @see MediaFormat#COLOR_TRANSFER_ST2084
+             * @see MediaFormat#COLOR_TRANSFER_HLG
              */
             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
             public static final String COLOR_TRANSFER = "color_transfer";
 
             /**
-             * The range of color aspects
-             * @hide
+             * The color range of this media file, if available.
+             *
+             * @see MediaFormat#COLOR_RANGE_LIMITED
+             * @see MediaFormat#COLOR_RANGE_FULL
              */
             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
             public static final String COLOR_RANGE = "color_range";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 457dcc0..065ee0b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8241,20 +8241,6 @@
         public static final String AWARE_LOCK_ENABLED = "aware_lock_enabled";
 
         /**
-         * The settings values which should only be restored if the target device is the
-         * same as the source device
-         *
-         * NOTE: Settings are backed up and restored in the order they appear
-         *       in this array. If you have one setting depending on another,
-         *       make sure that they are ordered appropriately.
-         *
-         * @hide
-         */
-        public static final String[] DEVICE_SPECIFIC_SETTINGS_TO_BACKUP = {
-                DISPLAY_DENSITY_FORCED,
-        };
-
-        /**
          * Keys we no longer back up under the current schema, but want to continue to
          * process when restoring historical backup datasets.
          *
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index f1c870d..dd2586c 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -126,11 +126,29 @@
             = "android.service.quicksettings.ACTIVE_TILE";
 
     /**
+     * Meta-data for a tile to support {@code BooleanState}.
+     * <p>
+     * BooleanState is for tiles that should support switch tile behavior in accessibility. This is
+     * the behavior of most of the framework tiles.
+     *
+     * To make a TileService support BooleanState, set this meta-data to true on the TileService's
+     * manifest declaration.
+     * <pre class="prettyprint">
+     * {@literal
+     * <meta-data android:name="android.service.quicksettings.BOOLEAN_TILE"
+     *      android:value="true" />
+     * }
+     * </pre>
+     */
+    public static final String META_DATA_BOOLEAN_TILE =
+            "android.service.quicksettings.BOOLEAN_TILE";
+
+    /**
      * Used to notify SysUI that Listening has be requested.
      * @hide
      */
-    public static final String ACTION_REQUEST_LISTENING
-            = "android.service.quicksettings.action.REQUEST_LISTENING";
+    public static final String ACTION_REQUEST_LISTENING =
+            "android.service.quicksettings.action.REQUEST_LISTENING";
 
     /**
      * @hide
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index 20e0d14..bc5edf8 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -255,7 +255,7 @@
                     if (value == 0 && flagsWasZero) {
                         return constNameWithoutPrefix(prefix, field);
                     }
-                    if ((flags & value) == value) {
+                    if (value != 0 && (flags & value) == value) {
                         flags &= ~value;
                         res.append(constNameWithoutPrefix(prefix, field)).append('|');
                     }
diff --git a/core/java/android/util/TimestampedValue.java b/core/java/android/util/TimestampedValue.java
index 1289e4d..4505673 100644
--- a/core/java/android/util/TimestampedValue.java
+++ b/core/java/android/util/TimestampedValue.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.SystemClock;
 
 import java.util.Objects;
@@ -30,14 +31,14 @@
  * If a suitable clock is used the reference time can be used to identify the age of a value or
  * ordering between values.
  *
- * <p>To read and write a timestamped value from / to a Parcel see
- * {@link #readFromParcel(Parcel, ClassLoader, Class)} and
- * {@link #writeToParcel(Parcel, TimestampedValue)}.
+ * <p>This class implements {@link Parcelable} for convenience but instances will only actually be
+ * parcelable if the value type held is {@code null}, {@link Parcelable}, or one of the other types
+ * supported by {@link Parcel#writeValue(Object)} / {@link Parcel#readValue(ClassLoader)}.
  *
  * @param <T> the type of the value with an associated timestamp
  * @hide
  */
-public final class TimestampedValue<T> {
+public final class TimestampedValue<T> implements Parcelable {
     private final long mReferenceTimeMillis;
     private final T mValue;
 
@@ -81,57 +82,43 @@
     }
 
     /**
-     * Read a {@link TimestampedValue} from a parcel that was stored using
-     * {@link #writeToParcel(Parcel, TimestampedValue)}.
-     *
-     * <p>The marshalling/unmarshalling of the value relies upon {@link Parcel#writeValue(Object)}
-     * and {@link Parcel#readValue(ClassLoader)} and so this method can only be used with types
-     * supported by those methods.
-     *
-     * @param in the Parcel to read from
-     * @param classLoader the ClassLoader to pass to {@link Parcel#readValue(ClassLoader)}
-     * @param valueClass the expected type of the value, typically the same as {@code <T>} but can
-     *     also be a subclass
-     * @throws RuntimeException if the value read is not compatible with {@code valueClass} or the
-     *     object could not be read
-     */
-    @SuppressWarnings("unchecked")
-    @NonNull
-    public static <T> TimestampedValue<T> readFromParcel(
-            @NonNull Parcel in, @Nullable ClassLoader classLoader, Class<? extends T> valueClass) {
-        long referenceTimeMillis = in.readLong();
-        T value = (T) in.readValue(classLoader);
-        // Equivalent to static code: if (!(value.getClass() instanceof {valueClass})) {
-        if (value != null && !valueClass.isAssignableFrom(value.getClass())) {
-            throw new RuntimeException("Value was of type " + value.getClass()
-                    + " is not assignable to " + valueClass);
-        }
-        return new TimestampedValue<>(referenceTimeMillis, value);
-    }
-
-    /**
-     * Write a {@link TimestampedValue} to a parcel so that it can be read using
-     * {@link #readFromParcel(Parcel, ClassLoader, Class)}.
-     *
-     * <p>The marshalling/unmarshalling of the value relies upon {@link Parcel#writeValue(Object)}
-     * and {@link Parcel#readValue(ClassLoader)} and so this method can only be used with types
-     * supported by those methods.
-     *
-     * @param dest the Parcel
-     * @param timestampedValue the value
-     * @throws RuntimeException if the value could not be written to the Parcel
-     */
-    public static void writeToParcel(
-            @NonNull Parcel dest, @NonNull TimestampedValue<?> timestampedValue) {
-        dest.writeLong(timestampedValue.mReferenceTimeMillis);
-        dest.writeValue(timestampedValue.mValue);
-    }
-
-    /**
      * Returns the difference in milliseconds between two instance's reference times.
      */
     public static long referenceTimeDifference(
             @NonNull TimestampedValue<?> one, @NonNull TimestampedValue<?> two) {
         return one.mReferenceTimeMillis - two.mReferenceTimeMillis;
     }
+
+    public static final @NonNull Parcelable.Creator<TimestampedValue<?>> CREATOR =
+            new Parcelable.ClassLoaderCreator<TimestampedValue<?>>() {
+
+                @Override
+                public TimestampedValue<?> createFromParcel(@NonNull Parcel source) {
+                    return createFromParcel(source, null);
+                }
+
+                @Override
+                public TimestampedValue<?> createFromParcel(
+                        @NonNull Parcel source, @Nullable ClassLoader classLoader) {
+                    long referenceTimeMillis = source.readLong();
+                    Object value = source.readValue(classLoader);
+                    return new TimestampedValue<>(referenceTimeMillis, value);
+                }
+
+                @Override
+                public TimestampedValue[] newArray(int size) {
+                    return new TimestampedValue[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeLong(mReferenceTimeMillis);
+        dest.writeValue(mValue);
+    }
 }
diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl
index 955be8d..762366e 100644
--- a/core/java/android/view/IRecentsAnimationController.aidl
+++ b/core/java/android/view/IRecentsAnimationController.aidl
@@ -120,4 +120,10 @@
      * @see IRecentsAnimationRunner#onCancelled
      */
     void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot);
+
+    /**
+     * Sets a state for controller to decide which surface is the destination when the recents
+     * animation is cancelled through fail safe mechanism.
+     */
+    void setWillFinishToHome(boolean willFinishToHome);
 }
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 4b872d3..8bf99ec 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -81,6 +81,14 @@
      */
     void showInsets(int types, boolean fromIme);
 
+    /**
+     * Called when a set of insets source window should be hidden by policy.
+     *
+     * @param types internal inset types (WindowInsets.Type.InsetType) to hide
+     * @param fromIme true if this request originated from IME (InputMethodService).
+     */
+    void hideInsets(int types, boolean fromIme);
+
     void moved(int newX, int newY);
     void dispatchAppVisibility(boolean visible);
     void dispatchGetNewSurface();
diff --git a/core/java/android/view/InputMonitor.java b/core/java/android/view/InputMonitor.java
index 1a1d7e6..ad1f201 100644
--- a/core/java/android/view/InputMonitor.java
+++ b/core/java/android/view/InputMonitor.java
@@ -40,8 +40,6 @@
     private static final boolean DEBUG = false;
 
     @NonNull
-    private final String mName;
-    @NonNull
     private final InputChannel mInputChannel;
     @NonNull
     private final IInputMonitorHost mHost;
@@ -81,23 +79,19 @@
 
 
 
-    // Code below generated by codegen v1.0.1.
+    // Code below generated by codegen v1.0.7.
     //
     // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
     // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/InputMonitor.java
-    //
-    // CHECKSTYLE:OFF Generated code
+
 
     @DataClass.Generated.Member
     public InputMonitor(
-            @NonNull String name,
             @NonNull InputChannel inputChannel,
             @NonNull IInputMonitorHost host) {
-        this.mName = name;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mName);
         this.mInputChannel = inputChannel;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mInputChannel);
@@ -109,11 +103,6 @@
     }
 
     @DataClass.Generated.Member
-    public @NonNull String getName() {
-        return mName;
-    }
-
-    @DataClass.Generated.Member
     public @NonNull InputChannel getInputChannel() {
         return mInputChannel;
     }
@@ -130,7 +119,6 @@
         // String fieldNameToString() { ... }
 
         return "InputMonitor { " +
-                "name = " + mName + ", " +
                 "inputChannel = " + mInputChannel + ", " +
                 "host = " + mHost +
         " }";
@@ -142,7 +130,6 @@
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
-        dest.writeString(mName);
         dest.writeTypedObject(mInputChannel, flags);
         dest.writeStrongInterface(mHost);
     }
@@ -151,6 +138,26 @@
     @DataClass.Generated.Member
     public int describeContents() { return 0; }
 
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ InputMonitor(Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        InputChannel inputChannel = (InputChannel) in.readTypedObject(InputChannel.CREATOR);
+        IInputMonitorHost host = IInputMonitorHost.Stub.asInterface(in.readStrongBinder());
+
+        this.mInputChannel = inputChannel;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInputChannel);
+        this.mHost = host;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mHost);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
     @DataClass.Generated.Member
     public static final @NonNull Parcelable.Creator<InputMonitor> CREATOR
             = new Parcelable.Creator<InputMonitor>() {
@@ -160,26 +167,16 @@
         }
 
         @Override
-        @SuppressWarnings({"unchecked", "RedundantCast"})
         public InputMonitor createFromParcel(Parcel in) {
-            // You can override field unparcelling by defining methods like:
-            // static FieldType unparcelFieldName(Parcel in) { ... }
-
-            String name = in.readString();
-            InputChannel inputChannel = (InputChannel) in.readTypedObject(InputChannel.CREATOR);
-            IInputMonitorHost host = IInputMonitorHost.Stub.asInterface(in.readStrongBinder());
-            return new InputMonitor(
-                    name,
-                    inputChannel,
-                    host);
+            return new InputMonitor(in);
         }
     };
 
     @DataClass.Generated(
-            time = 1569871940995L,
-            codegenVersion = "1.0.1",
+            time = 1571177265149L,
+            codegenVersion = "1.0.7",
             sourceFile = "frameworks/base/core/java/android/view/InputMonitor.java",
-            inputSignatures = "private static final  java.lang.String TAG\nprivate static final  boolean DEBUG\nprivate final @android.annotation.NonNull java.lang.String mName\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\npublic  void pilferPointers()\npublic  void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
+            inputSignatures = "private static final  java.lang.String TAG\nprivate static final  boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\npublic  void pilferPointers()\npublic  void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 10a9aaa..08e4eeb 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -38,10 +38,8 @@
     // The input application handle.
     public final InputApplicationHandle inputApplicationHandle;
 
-    // The client window.
-    public final IWindow clientWindow;
-
-    // The token associated with the window.
+    // The token associates input data with a window and its input channel. The client input
+    // channel and the server input channel will both contain this token.
     public IBinder token;
 
     // The window name.
@@ -120,10 +118,8 @@
 
     private native void nativeDispose();
 
-    public InputWindowHandle(InputApplicationHandle inputApplicationHandle,
-            IWindow clientWindow, int displayId) {
+    public InputWindowHandle(InputApplicationHandle inputApplicationHandle, int displayId) {
         this.inputApplicationHandle = inputApplicationHandle;
-        this.clientWindow = clientWindow;
         this.displayId = displayId;
     }
 
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 341c214..e4deffa 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -229,6 +229,10 @@
             final InsetsSourceConsumer consumer = items.valueAt(i);
             final InsetsSource source = mInitialInsetsState.getSource(consumer.getType());
             final InsetsSourceControl control = consumer.getControl();
+            if (control == null) {
+                // Control may not be available for consumer yet or revoked.
+                continue;
+            }
             final SurfaceControl leash = consumer.getControl().getLeash();
 
             mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 5a8636d..5bb4f63 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -251,6 +251,10 @@
 
     @Override
     public void hide(@InsetType int types) {
+        hide(types, false /* fromIme */);
+    }
+
+    void hide(@InsetType int types, boolean fromIme) {
         int typesReady = 0;
         final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
@@ -265,7 +269,7 @@
             }
             typesReady |= InsetsState.toPublicType(consumer.getType());
         }
-        applyAnimation(typesReady, false /* show */, false /* fromIme */);
+        applyAnimation(typesReady, false /* show */, fromIme /* fromIme */);
     }
 
     @Override
@@ -331,42 +335,35 @@
         boolean isReady = true;
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
             InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
-            // Double check for IME that IME target window has focus.
-            if (consumer.getType() != TYPE_IME || consumer.hasWindowFocus()) {
-                boolean setVisible = !consumer.isVisible();
-                if (setVisible) {
-                    // Show request
-                    switch(consumer.requestShow(fromIme)) {
-                        case ShowResult.SHOW_IMMEDIATELY:
-                            typesReady |= InsetsState.toPublicType(consumer.getType());
-                            break;
-                        case ShowResult.SHOW_DELAYED:
-                            isReady = false;
-                            break;
-                        case ShowResult.SHOW_FAILED:
-                            // IME cannot be shown (since it didn't have focus), proceed
-                            // with animation of other types.
-                            if (mPendingTypesToShow != 0) {
-                                // remove IME from pending because view no longer has focus.
-                                mPendingTypesToShow &= ~InsetsState.toPublicType(TYPE_IME);
-                            }
-                            break;
-                    }
-                } else {
-                    // Hide request
-                    // TODO: Move notifyHidden() to beginning of the hide animation
-                    // (when visibility actually changes using hideDirectly()).
-                    consumer.notifyHidden();
-                    typesReady |= InsetsState.toPublicType(consumer.getType());
+            boolean setVisible = !consumer.isVisible();
+            if (setVisible) {
+                // Show request
+                switch(consumer.requestShow(fromIme)) {
+                    case ShowResult.SHOW_IMMEDIATELY:
+                        typesReady |= InsetsState.toPublicType(consumer.getType());
+                        break;
+                    case ShowResult.SHOW_DELAYED:
+                        isReady = false;
+                        break;
+                    case ShowResult.SHOW_FAILED:
+                        // IME cannot be shown (since it didn't have focus), proceed
+                        // with animation of other types.
+                        if (mPendingTypesToShow != 0) {
+                            // remove IME from pending because view no longer has focus.
+                            mPendingTypesToShow &= ~InsetsState.toPublicType(TYPE_IME);
+                        }
+                        break;
                 }
-                consumers.put(consumer.getType(), consumer);
             } else {
-                // window doesnt have focus, no-op.
-                isReady = false;
-                // TODO: Let the calling app know that window has lost focus and
-                //       show()/hide()/controlWindowInsetsAnimation requests will be ignored.
-                typesReady &= ~InsetsState.toPublicType(consumer.getType());
+                // Hide request
+                // TODO: Move notifyHidden() to beginning of the hide animation
+                // (when visibility actually changes using hideDirectly()).
+                if (!fromIme) {
+                    consumer.notifyHidden();
+                }
+                typesReady |= InsetsState.toPublicType(consumer.getType());
             }
+            consumers.put(consumer.getType(), consumer);
         }
         return new Pair<>(typesReady, isReady);
     }
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 8a1fd62..ad59ae5 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -32,6 +32,7 @@
 import android.os.Debug;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -46,6 +47,7 @@
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -53,12 +55,12 @@
 import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.util.ArrayDeque;
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.concurrent.Callable;
-import java.util.concurrent.CancellationException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -68,6 +70,7 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Function;
+import java.util.stream.Stream;
 
 /**
  * Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -331,11 +334,83 @@
         public View findHierarchyView(String className, int hashCode);
     }
 
-    private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
-    private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
+    private abstract static class PropertyInfo<T extends Annotation,
+            R extends AccessibleObject & Member> {
+
+        public final R member;
+        public final T property;
+        public final String name;
+        public final Class<?> returnType;
+
+        public String entrySuffix = "";
+        public String valueSuffix = "";
+
+        PropertyInfo(Class<T> property, R member, Class<?> returnType) {
+            this.member = member;
+            this.name = member.getName();
+            this.property = member.getAnnotation(property);
+            this.returnType = returnType;
+        }
+
+        public abstract Object invoke(Object target) throws Exception;
+
+        static <T extends Annotation> PropertyInfo<T, ?> forMethod(Method method,
+                Class<T> property) {
+            // Ensure the method return and parameter types can be resolved.
+            try {
+                if ((method.getReturnType() == Void.class)
+                        || (method.getParameterTypes().length != 0)) {
+                    return null;
+                }
+            } catch (NoClassDefFoundError e) {
+                return null;
+            }
+            if (!method.isAnnotationPresent(property)) {
+                return null;
+            }
+            method.setAccessible(true);
+
+            PropertyInfo info = new MethodPI(method, property);
+            info.entrySuffix = "()";
+            info.valueSuffix = ";";
+            return info;
+        }
+
+        static <T extends Annotation> PropertyInfo<T, ?> forField(Field field, Class<T> property) {
+            if (!field.isAnnotationPresent(property)) {
+                return null;
+            }
+            field.setAccessible(true);
+            return new FieldPI<>(field, property);
+        }
+    }
+
+    private static class MethodPI<T extends Annotation> extends PropertyInfo<T, Method> {
+
+        MethodPI(Method method, Class<T> property) {
+            super(property, method, method.getReturnType());
+        }
+
+        @Override
+        public Object invoke(Object target) throws Exception {
+            return member.invoke(target);
+        }
+    }
+
+    private static class FieldPI<T extends Annotation> extends PropertyInfo<T, Field> {
+
+        FieldPI(Field field, Class<T> property) {
+            super(property, field, field.getType());
+        }
+
+        @Override
+        public Object invoke(Object target) throws Exception {
+            return member.get(target);
+        }
+    }
 
     // Maximum delay in ms after which we stop trying to capture a View's drawing
-    private static final int CAPTURE_TIMEOUT = 4000;
+    private static final int CAPTURE_TIMEOUT = 6000;
 
     private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
     private static final String REMOTE_COMMAND_DUMP = "DUMP";
@@ -346,9 +421,9 @@
     private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
     private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
 
-    private static HashMap<Class<?>, Field[]> sFieldsForClasses;
-    private static HashMap<Class<?>, Method[]> sMethodsForClasses;
-    private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
+    private static HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> sExportProperties;
+    private static HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]>
+            sCapturedViewProperties;
 
     /**
      * @deprecated This enum is now unused
@@ -1157,6 +1232,69 @@
 
     private static void dumpViewHierarchy(Context context, ViewGroup group,
             BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
+        cacheExportedProperties(group.getClass());
+        if (!skipChildren) {
+            cacheExportedPropertiesForChildren(group);
+        }
+        // Try to use the handler provided by the view
+        Handler handler = group.getHandler();
+        // Fall back on using the main thread
+        if (handler == null) {
+            handler = new Handler(Looper.getMainLooper());
+        }
+
+        if (handler.getLooper() == Looper.myLooper()) {
+            dumpViewHierarchyOnUIThread(context, group, out, level, skipChildren,
+                    includeProperties);
+        } else {
+            FutureTask task = new FutureTask(() ->
+                    dumpViewHierarchyOnUIThread(context, group, out, level, skipChildren,
+                            includeProperties), null);
+            Message msg = Message.obtain(handler, task);
+            msg.setAsynchronous(true);
+            handler.sendMessage(msg);
+            while (true) {
+                try {
+                    task.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
+                    return;
+                } catch (InterruptedException e) {
+                    // try again
+                } catch (ExecutionException | TimeoutException e) {
+                    // Something unexpected happened.
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    private static void cacheExportedPropertiesForChildren(ViewGroup group) {
+        final int count = group.getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View view = group.getChildAt(i);
+            cacheExportedProperties(view.getClass());
+            if (view instanceof ViewGroup) {
+                cacheExportedPropertiesForChildren((ViewGroup) view);
+            }
+        }
+    }
+
+    private static void cacheExportedProperties(Class<?> klass) {
+        if (sExportProperties != null && sExportProperties.containsKey(klass)) {
+            return;
+        }
+        do {
+            for (PropertyInfo<ExportedProperty, ?> info : getExportedProperties(klass)) {
+                if (!info.returnType.isPrimitive() && info.property.deepExport()) {
+                    cacheExportedProperties(info.returnType);
+                }
+            }
+            klass = klass.getSuperclass();
+        } while (klass != Object.class);
+    }
+
+
+    private static void dumpViewHierarchyOnUIThread(Context context, ViewGroup group,
+            BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
         if (!dumpView(context, group, out, level, includeProperties)) {
             return;
         }
@@ -1169,16 +1307,16 @@
         for (int i = 0; i < count; i++) {
             final View view = group.getChildAt(i);
             if (view instanceof ViewGroup) {
-                dumpViewHierarchy(context, (ViewGroup) view, out, level + 1, skipChildren,
-                        includeProperties);
+                dumpViewHierarchyOnUIThread(context, (ViewGroup) view, out, level + 1,
+                        skipChildren, includeProperties);
             } else {
                 dumpView(context, view, out, level + 1, includeProperties);
             }
             if (view.mOverlay != null) {
                 ViewOverlay overlay = view.getOverlay();
                 ViewGroup overlayContainer = overlay.mOverlayViewGroup;
-                dumpViewHierarchy(context, overlayContainer, out, level + 2, skipChildren,
-                        includeProperties);
+                dumpViewHierarchyOnUIThread(context, overlayContainer, out, level + 2,
+                        skipChildren, includeProperties);
             }
         }
         if (group instanceof HierarchyHandler) {
@@ -1212,81 +1350,28 @@
         return true;
     }
 
-    private static Field[] getExportedPropertyFields(Class<?> klass) {
-        if (sFieldsForClasses == null) {
-            sFieldsForClasses = new HashMap<Class<?>, Field[]>();
-        }
-        if (sAnnotations == null) {
-            sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
-        }
-
-        final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
-
-        Field[] fields = map.get(klass);
-        if (fields != null) {
-            return fields;
-        }
-
-        try {
-            final Field[] declaredFields = klass.getDeclaredFieldsUnchecked(false);
-            final ArrayList<Field> foundFields = new ArrayList<Field>();
-            for (final Field field : declaredFields) {
-              // Fields which can't be resolved have a null type.
-              if (field.getType() != null && field.isAnnotationPresent(ExportedProperty.class)) {
-                  field.setAccessible(true);
-                  foundFields.add(field);
-                  sAnnotations.put(field, field.getAnnotation(ExportedProperty.class));
-              }
-            }
-            fields = foundFields.toArray(new Field[foundFields.size()]);
-            map.put(klass, fields);
-        } catch (NoClassDefFoundError e) {
-            throw new AssertionError(e);
-        }
-
-        return fields;
+    private static <T extends Annotation> PropertyInfo<T, ?>[] convertToPropertyInfos(
+            Method[] methods, Field[] fields, Class<T> property) {
+        return Stream.of(Arrays.stream(methods).map(m -> PropertyInfo.forMethod(m, property)),
+                Arrays.stream(fields).map(f -> PropertyInfo.forField(f, property)))
+                .flatMap(Function.identity())
+                .filter(i -> i != null)
+                .toArray(PropertyInfo[]::new);
     }
 
-    private static Method[] getExportedPropertyMethods(Class<?> klass) {
-        if (sMethodsForClasses == null) {
-            sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
+    private static PropertyInfo<ExportedProperty, ?>[] getExportedProperties(Class<?> klass) {
+        if (sExportProperties == null) {
+            sExportProperties = new HashMap<>();
         }
-        if (sAnnotations == null) {
-            sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
+        final HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> map = sExportProperties;
+        PropertyInfo<ExportedProperty, ?>[] properties = sExportProperties.get(klass);
+
+        if (properties == null) {
+            properties = convertToPropertyInfos(klass.getDeclaredMethodsUnchecked(false),
+                    klass.getDeclaredFieldsUnchecked(false), ExportedProperty.class);
+            map.put(klass, properties);
         }
-
-        final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
-
-        Method[] methods = map.get(klass);
-        if (methods != null) {
-            return methods;
-        }
-
-        methods = klass.getDeclaredMethodsUnchecked(false);
-
-        final ArrayList<Method> foundMethods = new ArrayList<Method>();
-        for (final Method method : methods) {
-            // Ensure the method return and parameter types can be resolved.
-            try {
-                method.getReturnType();
-                method.getParameterTypes();
-            } catch (NoClassDefFoundError e) {
-                continue;
-            }
-
-            if (method.getParameterTypes().length == 0 &&
-                    method.isAnnotationPresent(ExportedProperty.class) &&
-                    method.getReturnType() != Void.class) {
-                method.setAccessible(true);
-                foundMethods.add(method);
-                sAnnotations.put(method, method.getAnnotation(ExportedProperty.class));
-            }
-        }
-
-        methods = foundMethods.toArray(new Method[foundMethods.size()]);
-        map.put(klass, methods);
-
-        return methods;
+        return properties;
     }
 
     private static void dumpViewProperties(Context context, Object view,
@@ -1305,233 +1390,97 @@
 
         Class<?> klass = view.getClass();
         do {
-            exportFields(context, view, out, klass, prefix);
-            exportMethods(context, view, out, klass, prefix);
+            writeExportedProperties(context, view, out, klass, prefix);
             klass = klass.getSuperclass();
         } while (klass != Object.class);
     }
 
-    private static Object callMethodOnAppropriateTheadBlocking(final Method method,
-            final Object object) throws IllegalAccessException, InvocationTargetException,
-            TimeoutException {
-        if (!(object instanceof View)) {
-            return method.invoke(object, (Object[]) null);
-        }
-
-        final View view = (View) object;
-        Callable<Object> callable = new Callable<Object>() {
-            @Override
-            public Object call() throws IllegalAccessException, InvocationTargetException {
-                return method.invoke(view, (Object[]) null);
-            }
-        };
-        FutureTask<Object> future = new FutureTask<Object>(callable);
-        // Try to use the handler provided by the view
-        Handler handler = view.getHandler();
-        // Fall back on using the main thread
-        if (handler == null) {
-            handler = new Handler(android.os.Looper.getMainLooper());
-        }
-        handler.post(future);
-        while (true) {
-            try {
-                return future.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
-            } catch (ExecutionException e) {
-                Throwable t = e.getCause();
-                if (t instanceof IllegalAccessException) {
-                    throw (IllegalAccessException)t;
-                }
-                if (t instanceof InvocationTargetException) {
-                    throw (InvocationTargetException)t;
-                }
-                throw new RuntimeException("Unexpected exception", t);
-            } catch (InterruptedException e) {
-                // Call get again
-            } catch (CancellationException e) {
-                throw new RuntimeException("Unexpected cancellation exception", e);
-            }
-        }
-    }
-
     private static String formatIntToHexString(int value) {
         return "0x" + Integer.toHexString(value).toUpperCase();
     }
 
-    private static void exportMethods(Context context, Object view, BufferedWriter out,
+    private static void writeExportedProperties(Context context, Object view, BufferedWriter out,
             Class<?> klass, String prefix) throws IOException {
-
-        final Method[] methods = getExportedPropertyMethods(klass);
-        int count = methods.length;
-        for (int i = 0; i < count; i++) {
-            final Method method = methods[i];
+        for (PropertyInfo<ExportedProperty, ?> info : getExportedProperties(klass)) {
             //noinspection EmptyCatchBlock
+            Object value;
             try {
-                Object methodValue = callMethodOnAppropriateTheadBlocking(method, view);
-                final Class<?> returnType = method.getReturnType();
-                final ExportedProperty property = sAnnotations.get(method);
-                String categoryPrefix =
-                        property.category().length() != 0 ? property.category() + ":" : "";
-
-                if (returnType == int.class) {
-                    if (property.resolveId() && context != null) {
-                        final int id = (Integer) methodValue;
-                        methodValue = resolveId(context, id);
-                    } else {
-                        final FlagToString[] flagsMapping = property.flagMapping();
-                        if (flagsMapping.length > 0) {
-                            final int intValue = (Integer) methodValue;
-                            final String valuePrefix =
-                                    categoryPrefix + prefix + method.getName() + '_';
-                            exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
-                        }
-
-                        final IntToString[] mapping = property.mapping();
-                        if (mapping.length > 0) {
-                            final int intValue = (Integer) methodValue;
-                            boolean mapped = false;
-                            int mappingCount = mapping.length;
-                            for (int j = 0; j < mappingCount; j++) {
-                                final IntToString mapper = mapping[j];
-                                if (mapper.from() == intValue) {
-                                    methodValue = mapper.to();
-                                    mapped = true;
-                                    break;
-                                }
-                            }
-
-                            if (!mapped) {
-                                methodValue = intValue;
-                            }
-                        }
-                    }
-                } else if (returnType == int[].class) {
-                    final int[] array = (int[]) methodValue;
-                    final String valuePrefix = categoryPrefix + prefix + method.getName() + '_';
-                    final String suffix = "()";
-
-                    exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
-
-                    continue;
-                } else if (returnType == String[].class) {
-                    final String[] array = (String[]) methodValue;
-                    if (property.hasAdjacentMapping() && array != null) {
-                        for (int j = 0; j < array.length; j += 2) {
-                            if (array[j] != null) {
-                                writeEntry(out, categoryPrefix + prefix, array[j], "()",
-                                        array[j + 1] == null ? "null" : array[j + 1]);
-                            }
-
-                        }
-                    }
-
-                    continue;
-                } else if (!returnType.isPrimitive()) {
-                    if (property.deepExport()) {
-                        dumpViewProperties(context, methodValue, out, prefix + property.prefix());
-                        continue;
-                    }
-                }
-
-                writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue);
-            } catch (IllegalAccessException e) {
-            } catch (InvocationTargetException e) {
-            } catch (TimeoutException e) {
+                value = info.invoke(view);
+            } catch (Exception e) {
+                // ignore
+                continue;
             }
-        }
-    }
 
-    private static void exportFields(Context context, Object view, BufferedWriter out,
-            Class<?> klass, String prefix) throws IOException {
+            String categoryPrefix =
+                    info.property.category().length() != 0 ? info.property.category() + ":" : "";
 
-        final Field[] fields = getExportedPropertyFields(klass);
+            if (info.returnType == int.class || info.returnType == byte.class) {
+                if (info.property.resolveId() && context != null) {
+                    final int id = (Integer) value;
+                    value = resolveId(context, id);
 
-        int count = fields.length;
-        for (int i = 0; i < count; i++) {
-            final Field field = fields[i];
-
-            //noinspection EmptyCatchBlock
-            try {
-                Object fieldValue = null;
-                final Class<?> type = field.getType();
-                final ExportedProperty property = sAnnotations.get(field);
-                String categoryPrefix =
-                        property.category().length() != 0 ? property.category() + ":" : "";
-
-                if (type == int.class || type == byte.class) {
-                    if (property.resolveId() && context != null) {
-                        final int id = field.getInt(view);
-                        fieldValue = resolveId(context, id);
-                    } else {
-                        final FlagToString[] flagsMapping = property.flagMapping();
-                        if (flagsMapping.length > 0) {
-                            final int intValue = field.getInt(view);
-                            final String valuePrefix =
-                                    categoryPrefix + prefix + field.getName() + '_';
-                            exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
-                        }
-
-                        final IntToString[] mapping = property.mapping();
-                        if (mapping.length > 0) {
-                            final int intValue = field.getInt(view);
-                            int mappingCount = mapping.length;
-                            for (int j = 0; j < mappingCount; j++) {
-                                final IntToString mapped = mapping[j];
-                                if (mapped.from() == intValue) {
-                                    fieldValue = mapped.to();
-                                    break;
-                                }
-                            }
-
-                            if (fieldValue == null) {
-                                fieldValue = intValue;
-                            }
-                        }
-
-                        if (property.formatToHexString()) {
-                            fieldValue = field.get(view);
-                            if (type == int.class) {
-                                fieldValue = formatIntToHexString((Integer) fieldValue);
-                            } else if (type == byte.class) {
-                                fieldValue = "0x"
-                                        + HexEncoding.encodeToString((Byte) fieldValue, true);
-                            }
-                        }
+                } else if (info.property.formatToHexString()) {
+                    if (info.returnType == int.class) {
+                        value = formatIntToHexString((Integer) value);
+                    } else if (info.returnType == byte.class) {
+                        value = "0x"
+                                + HexEncoding.encodeToString((Byte) value, true);
                     }
-                } else if (type == int[].class) {
-                    final int[] array = (int[]) field.get(view);
-                    final String valuePrefix = categoryPrefix + prefix + field.getName() + '_';
-                    final String suffix = "";
-
-                    exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
-
-                    continue;
-                } else if (type == String[].class) {
-                    final String[] array = (String[]) field.get(view);
-                    if (property.hasAdjacentMapping() && array != null) {
-                        for (int j = 0; j < array.length; j += 2) {
-                            if (array[j] != null) {
-                                writeEntry(out, categoryPrefix + prefix, array[j], "",
-                                        array[j + 1] == null ? "null" : array[j + 1]);
-                            }
-                        }
+                } else {
+                    final ViewDebug.FlagToString[] flagsMapping = info.property.flagMapping();
+                    if (flagsMapping.length > 0) {
+                        final int intValue = (Integer) value;
+                        final String valuePrefix =
+                                categoryPrefix + prefix + info.name + '_';
+                        exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
                     }
 
-                    continue;
-                } else if (!type.isPrimitive()) {
-                    if (property.deepExport()) {
-                        dumpViewProperties(context, field.get(view), out, prefix +
-                                property.prefix());
-                        continue;
+                    final ViewDebug.IntToString[] mapping = info.property.mapping();
+                    if (mapping.length > 0) {
+                        final int intValue = (Integer) value;
+                        boolean mapped = false;
+                        int mappingCount = mapping.length;
+                        for (int j = 0; j < mappingCount; j++) {
+                            final ViewDebug.IntToString mapper = mapping[j];
+                            if (mapper.from() == intValue) {
+                                value = mapper.to();
+                                mapped = true;
+                                break;
+                            }
+                        }
+
+                        if (!mapped) {
+                            value = intValue;
+                        }
+                    }
+                }
+            } else if (info.returnType == int[].class) {
+                final int[] array = (int[]) value;
+                final String valuePrefix = categoryPrefix + prefix + info.name + '_';
+                exportUnrolledArray(context, out, info.property, array, valuePrefix,
+                        info.entrySuffix);
+
+                continue;
+            } else if (info.returnType == String[].class) {
+                final String[] array = (String[]) value;
+                if (info.property.hasAdjacentMapping() && array != null) {
+                    for (int j = 0; j < array.length; j += 2) {
+                        if (array[j] != null) {
+                            writeEntry(out, categoryPrefix + prefix, array[j],
+                                    info.entrySuffix, array[j + 1] == null ? "null" : array[j + 1]);
+                        }
                     }
                 }
 
-                if (fieldValue == null) {
-                    fieldValue = field.get(view);
+                continue;
+            } else if (!info.returnType.isPrimitive()) {
+                if (info.property.deepExport()) {
+                    dumpViewProperties(context, value, out, prefix + info.property.prefix());
+                    continue;
                 }
-
-                writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue);
-            } catch (IllegalAccessException e) {
             }
+
+            writeEntry(out, categoryPrefix + prefix, info.name, info.entrySuffix, value);
         }
     }
 
@@ -1721,91 +1670,40 @@
         }
     }
 
-    private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
-        if (mCapturedViewFieldsForClasses == null) {
-            mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
+    private static PropertyInfo<CapturedViewProperty, ?>[] getCapturedViewProperties(
+            Class<?> klass) {
+        if (sCapturedViewProperties == null) {
+            sCapturedViewProperties = new HashMap<>();
         }
-        final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
+        final HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]> map =
+                sCapturedViewProperties;
 
-        Field[] fields = map.get(klass);
-        if (fields != null) {
-            return fields;
+        PropertyInfo<CapturedViewProperty, ?>[] infos = map.get(klass);
+        if (infos == null) {
+            infos = convertToPropertyInfos(klass.getMethods(), klass.getFields(),
+                    CapturedViewProperty.class);
+            map.put(klass, infos);
         }
-
-        final ArrayList<Field> foundFields = new ArrayList<Field>();
-        fields = klass.getFields();
-
-        int count = fields.length;
-        for (int i = 0; i < count; i++) {
-            final Field field = fields[i];
-            if (field.isAnnotationPresent(CapturedViewProperty.class)) {
-                field.setAccessible(true);
-                foundFields.add(field);
-            }
-        }
-
-        fields = foundFields.toArray(new Field[foundFields.size()]);
-        map.put(klass, fields);
-
-        return fields;
+        return infos;
     }
 
-    private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
-        if (mCapturedViewMethodsForClasses == null) {
-            mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
-        }
-        final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
-
-        Method[] methods = map.get(klass);
-        if (methods != null) {
-            return methods;
-        }
-
-        final ArrayList<Method> foundMethods = new ArrayList<Method>();
-        methods = klass.getMethods();
-
-        int count = methods.length;
-        for (int i = 0; i < count; i++) {
-            final Method method = methods[i];
-            if (method.getParameterTypes().length == 0 &&
-                    method.isAnnotationPresent(CapturedViewProperty.class) &&
-                    method.getReturnType() != Void.class) {
-                method.setAccessible(true);
-                foundMethods.add(method);
-            }
-        }
-
-        methods = foundMethods.toArray(new Method[foundMethods.size()]);
-        map.put(klass, methods);
-
-        return methods;
-    }
-
-    private static String capturedViewExportMethods(Object obj, Class<?> klass,
-            String prefix) {
-
+    private static String exportCapturedViewProperties(Object obj, Class<?> klass, String prefix) {
         if (obj == null) {
             return "null";
         }
 
         StringBuilder sb = new StringBuilder();
-        final Method[] methods = capturedViewGetPropertyMethods(klass);
 
-        int count = methods.length;
-        for (int i = 0; i < count; i++) {
-            final Method method = methods[i];
+        for (PropertyInfo<CapturedViewProperty, ?> pi : getCapturedViewProperties(klass)) {
             try {
-                Object methodValue = method.invoke(obj, (Object[]) null);
-                final Class<?> returnType = method.getReturnType();
+                Object methodValue = pi.invoke(obj);
 
-                CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
-                if (property.retrieveReturn()) {
+                if (pi.property.retrieveReturn()) {
                     //we are interested in the second level data only
-                    sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
+                    sb.append(exportCapturedViewProperties(methodValue, pi.returnType,
+                            pi.name + "#"));
                 } else {
-                    sb.append(prefix);
-                    sb.append(method.getName());
-                    sb.append("()=");
+                    sb.append(prefix).append(pi.name).append(pi.entrySuffix).append("=");
 
                     if (methodValue != null) {
                         final String value = methodValue.toString().replace("\n", "\\n");
@@ -1813,47 +1711,10 @@
                     } else {
                         sb.append("null");
                     }
-                    sb.append("; ");
+                    sb.append(pi.valueSuffix).append(" ");
                 }
-            } catch (IllegalAccessException e) {
-                //Exception IllegalAccess, it is OK here
-                //we simply ignore this method
-            } catch (InvocationTargetException e) {
-                //Exception InvocationTarget, it is OK here
-                //we simply ignore this method
-            }
-        }
-        return sb.toString();
-    }
-
-    private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
-        if (obj == null) {
-            return "null";
-        }
-
-        StringBuilder sb = new StringBuilder();
-        final Field[] fields = capturedViewGetPropertyFields(klass);
-
-        int count = fields.length;
-        for (int i = 0; i < count; i++) {
-            final Field field = fields[i];
-            try {
-                Object fieldValue = field.get(obj);
-
-                sb.append(prefix);
-                sb.append(field.getName());
-                sb.append("=");
-
-                if (fieldValue != null) {
-                    final String value = fieldValue.toString().replace("\n", "\\n");
-                    sb.append(value);
-                } else {
-                    sb.append("null");
-                }
-                sb.append(' ');
-            } catch (IllegalAccessException e) {
-                //Exception IllegalAccess, it is OK here
-                //we simply ignore this field
+            } catch (Exception e) {
+                //It is OK here, we simply ignore this property
             }
         }
         return sb.toString();
@@ -1869,8 +1730,7 @@
     public static void dumpCapturedView(String tag, Object view) {
         Class<?> klass = view.getClass();
         StringBuilder sb = new StringBuilder(klass.getName() + ": ");
-        sb.append(capturedViewExportFields(view, klass, ""));
-        sb.append(capturedViewExportMethods(view, klass, ""));
+        sb.append(exportCapturedViewProperties(view, klass, ""));
         Log.d(tag, sb.toString());
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9ddd84f..20dc234 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4570,6 +4570,7 @@
     private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 32;
     private static final int MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED = 33;
     private static final int MSG_SHOW_INSETS = 34;
+    private static final int MSG_HIDE_INSETS = 35;
 
 
     final class ViewRootHandler extends Handler {
@@ -4636,6 +4637,8 @@
                     return "MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED";
                 case MSG_SHOW_INSETS:
                     return "MSG_SHOW_INSETS";
+                case MSG_HIDE_INSETS:
+                    return "MSG_HIDE_INSETS";
             }
             return super.getMessageName(message);
         }
@@ -4754,6 +4757,10 @@
                     mInsetsController.show(msg.arg1, msg.arg2 == 1);
                     break;
                 }
+                case MSG_HIDE_INSETS: {
+                    mInsetsController.hide(msg.arg1, msg.arg2 == 1);
+                    break;
+                }
                 case MSG_WINDOW_MOVED:
                     if (mAdded) {
                         final int w = mWinFrame.width();
@@ -7559,6 +7566,10 @@
         mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0).sendToTarget();
     }
 
+    private void hideInsets(@InsetType int types, boolean fromIme) {
+        mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0).sendToTarget();
+    }
+
     public void dispatchMoved(int newX, int newY) {
         if (DEBUG_LAYOUT) Log.v(mTag, "Window moved " + this + ": newX=" + newX + " newY=" + newY);
         if (mTranslator != null) {
@@ -8682,6 +8693,14 @@
         }
 
         @Override
+        public void hideInsets(@InsetType int types, boolean fromIme) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.hideInsets(types, fromIme);
+            }
+        }
+
+        @Override
         public void moved(int newX, int newY) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 4a6ef98..db76bb6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -197,12 +197,6 @@
     int TRANSIT_TASK_OPEN_BEHIND = 16;
 
     /**
-     * A window in a task is being animated in-place.
-     * @hide
-     */
-    int TRANSIT_TASK_IN_PLACE = 17;
-
-    /**
      * An activity is being relaunched (e.g. due to configuration change).
      * @hide
      */
@@ -286,7 +280,6 @@
             TRANSIT_WALLPAPER_INTRA_OPEN,
             TRANSIT_WALLPAPER_INTRA_CLOSE,
             TRANSIT_TASK_OPEN_BEHIND,
-            TRANSIT_TASK_IN_PLACE,
             TRANSIT_ACTIVITY_RELAUNCH,
             TRANSIT_DOCK_TASK_FROM_RECENTS,
             TRANSIT_KEYGUARD_GOING_AWAY,
@@ -1687,8 +1680,9 @@
          * to determine its default behavior.
          *
          * {@hide} */
-        @UnsupportedAppUsage
-        public static final int PRIVATE_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
+        @SystemApi
+        @RequiresPermission(permission.INTERNAL_SYSTEM_WINDOW)
+        public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
 
         /**
          * Never animate position changes of the window.
@@ -1849,6 +1843,7 @@
         @Retention(RetentionPolicy.SOURCE)
         @IntDef(flag = true, prefix = { "SYSTEM_FLAG_" }, value = {
                 SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
         })
         public @interface SystemFlags {}
 
@@ -1870,8 +1865,8 @@
                         equals = PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
                         name = "WANTS_OFFSET_NOTIFICATIONS"),
                 @ViewDebug.FlagToString(
-                        mask = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
-                        equals = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+                        mask = SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
+                        equals = SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
                         name = "SHOW_FOR_ALL_USERS"),
                 @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_NO_MOVE_ANIMATION,
diff --git a/core/java/android/webkit/FindAddress.java b/core/java/android/webkit/FindAddress.java
index 9183227..b146e3f6 100644
--- a/core/java/android/webkit/FindAddress.java
+++ b/core/java/android/webkit/FindAddress.java
@@ -154,7 +154,7 @@
 
     // A house number component is "one" or a number, optionally
     // followed by a single alphabetic character, or
-    private static final String HOUSE_COMPONENT = "(?:one|\\d+([a-z](?=[^a-z]|$)|st|nd|rd|th)?)";
+    private static final String HOUSE_COMPONENT = "(?:one|[0-9]+([a-z](?=[^a-z]|$)|st|nd|rd|th)?)";
 
     // House numbers are a repetition of |HOUSE_COMPONENT|, separated by -, and followed by
     // a delimiter character.
@@ -253,10 +253,10 @@
             Pattern.CASE_INSENSITIVE);
 
     private static final Pattern sSuffixedNumberRe =
-            Pattern.compile("(\\d+)(st|nd|rd|th)", Pattern.CASE_INSENSITIVE);
+            Pattern.compile("([0-9]+)(st|nd|rd|th)", Pattern.CASE_INSENSITIVE);
 
     private static final Pattern sZipCodeRe =
-            Pattern.compile("(?:\\d{5}(?:-\\d{4})?)" + WORD_END, Pattern.CASE_INSENSITIVE);
+            Pattern.compile("(?:[0-9]{5}(?:-[0-9]{4})?)" + WORD_END, Pattern.CASE_INSENSITIVE);
 
     private static boolean checkHouseNumber(String houseNumber) {
         // Make sure that there are at most 5 digits.
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 925a589..0b15cd0 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -246,7 +246,7 @@
                 Toast warningToast = mFrameworkObjectProvider.makeToastFromText(
                         mContext, toastMessage, Toast.LENGTH_LONG);
                 warningToast.getWindowParams().privateFlags |=
-                        WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+                        WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
                 warningToast.show();
             }
 
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 407a85f..068056f 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -388,21 +388,24 @@
         mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
                 mSystemWindowInsets.right, 0);
 
-        View emptyView = findViewById(R.id.empty);
-        if (emptyView != null) {
-            emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom
-                    + getResources().getDimensionPixelSize(
-                            R.dimen.chooser_edge_margin_normal) * 2);
-        }
-
-        if (mFooterSpacer == null) {
-            mFooterSpacer = new Space(getApplicationContext());
+        // Need extra padding so the list can fully scroll up
+        if (useLayoutWithDefault()) {
+            if (mFooterSpacer == null) {
+                mFooterSpacer = new Space(getApplicationContext());
+            } else {
+                ((ListView) mAdapterView).removeFooterView(mFooterSpacer);
+            }
+            mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
+                                                                       mSystemWindowInsets.bottom));
+            ((ListView) mAdapterView).addFooterView(mFooterSpacer);
         } else {
-            ((ListView) mAdapterView).removeFooterView(mFooterSpacer);
+            View emptyView = findViewById(R.id.empty);
+            if (emptyView != null) {
+                emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom
+                                     + getResources().getDimensionPixelSize(
+                                             R.dimen.chooser_edge_margin_normal) * 2);
+            }
         }
-        mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
-                mSystemWindowInsets.bottom));
-        ((ListView) mAdapterView).addFooterView(mFooterSpacer);
 
         resetButtonBar();
 
@@ -561,7 +564,7 @@
                         intent.getData().getHost(),
                         mAdapter.getFilteredItem().getDisplayLabel());
             } else if (mAdapter.areAllTargetsBrowsers()) {
-                dialogTitle =  getString(ActionTitle.BROWSABLE_TITLE_RES);
+                dialogTitle = getString(ActionTitle.BROWSABLE_TITLE_RES);
             } else {
                 dialogTitle = getString(ActionTitle.BROWSABLE_HOST_TITLE_RES,
                         intent.getData().getHost());
@@ -1304,6 +1307,7 @@
         // In case this method is called again (due to activity recreation), avoid adding a new
         // header if one is already present.
         if (useHeader && listView != null && listView.getHeaderViewsCount() == 0) {
+            listView.setHeaderDividersEnabled(true);
             listView.addHeaderView(LayoutInflater.from(this).inflate(
                     R.layout.resolver_different_item_header, listView, false));
         }
@@ -1346,11 +1350,13 @@
         final ViewGroup buttonLayout = findViewById(R.id.button_bar);
         if (buttonLayout != null) {
             buttonLayout.setVisibility(View.VISIBLE);
-            int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
-            buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
-                    buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
-                        R.dimen.resolver_button_bar_spacing) + inset);
 
+            if (!useLayoutWithDefault()) {
+                int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
+                buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
+                        buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
+                                R.dimen.resolver_button_bar_spacing) + inset);
+            }
             mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
             mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
 
@@ -2057,7 +2063,9 @@
             CharSequence subLabel = info.getExtendedInfo();
             if (TextUtils.equals(label, subLabel)) subLabel = null;
 
-            if (!TextUtils.equals(holder.text2.getText(), subLabel)) {
+            if (!TextUtils.equals(holder.text2.getText(), subLabel)
+                    && !TextUtils.isEmpty(subLabel)) {
+                holder.text2.setVisibility(View.VISIBLE);
                 holder.text2.setText(subLabel);
             }
 
diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java
index 0ed2524..1d4239f 100644
--- a/core/java/com/android/internal/app/procstats/AssociationState.java
+++ b/core/java/com/android/internal/app/procstats/AssociationState.java
@@ -950,6 +950,14 @@
 
         proto.write(PackageAssociationProcessStatsProto.COMPONENT_NAME, mName);
 
+        proto.write(PackageAssociationProcessStatsProto.TOTAL_COUNT, mTotalCount);
+        proto.write(PackageAssociationProcessStatsProto.TOTAL_DURATION_MS, getTotalDuration(now));
+        if (mTotalActiveCount != 0) {
+            proto.write(PackageAssociationProcessStatsProto.ACTIVE_COUNT, mTotalActiveCount);
+            proto.write(PackageAssociationProcessStatsProto.ACTIVE_DURATION_MS,
+                    getActiveDuration(now));
+        }
+
         final int NSRC = mSources.size();
         for (int isrc = 0; isrc < NSRC; isrc++) {
             final SourceKey key = mSources.keyAt(isrc);
diff --git a/core/java/com/android/internal/compat/CompatibilityChangeConfig.aidl b/core/java/com/android/internal/compat/CompatibilityChangeConfig.aidl
new file mode 100644
index 0000000..434c1b8
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityChangeConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.compat;
+
+parcelable CompatibilityChangeConfig;
diff --git a/core/java/com/android/internal/compat/CompatibilityChangeConfig.java b/core/java/com/android/internal/compat/CompatibilityChangeConfig.java
new file mode 100644
index 0000000..fd2ada0
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityChangeConfig.java
@@ -0,0 +1,95 @@
+/*
+ * 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.compat;
+
+
+import android.compat.Compatibility.ChangeConfig;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Parcelable containing compat config overrides for a given application.
+ * @hide
+ */
+public final class CompatibilityChangeConfig implements Parcelable {
+    private final ChangeConfig mChangeConfig;
+
+    public CompatibilityChangeConfig(ChangeConfig changeConfig) {
+        mChangeConfig = changeConfig;
+    }
+
+    /**
+     * Changes forced to be enabled.
+     */
+    public Set<Long> enabledChanges() {
+        return mChangeConfig.forceEnabledSet();
+    }
+
+    /**
+     * Changes forced to be disabled.
+     */
+    public Set<Long> disabledChanges() {
+        return mChangeConfig.forceDisabledSet();
+    }
+
+    private CompatibilityChangeConfig(Parcel in) {
+        long[] enabledArray = in.createLongArray();
+        long[] disabledArray = in.createLongArray();
+        Set<Long> enabled = toLongSet(enabledArray);
+        Set<Long> disabled = toLongSet(disabledArray);
+        mChangeConfig = new ChangeConfig(enabled, disabled);
+    }
+
+    private static Set<Long> toLongSet(long[] values) {
+        Set<Long> ret = new HashSet<>();
+        for (long value: values) {
+            ret.add(value);
+        }
+        return ret;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        long[] enabled = mChangeConfig.forceEnabledChangesArray();
+        long[] disabled = mChangeConfig.forceDisabledChangesArray();
+
+        dest.writeLongArray(enabled);
+        dest.writeLongArray(disabled);
+    }
+
+    public static final Parcelable.Creator<CompatibilityChangeConfig> CREATOR =
+            new Parcelable.Creator<CompatibilityChangeConfig>() {
+
+                @Override
+                public CompatibilityChangeConfig createFromParcel(Parcel in) {
+                    return new CompatibilityChangeConfig(in);
+                }
+
+                @Override
+                public CompatibilityChangeConfig[] newArray(int size) {
+                    return new CompatibilityChangeConfig[size];
+                }
+            };
+}
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 4d8378a..4099cfa 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -18,6 +18,8 @@
 
 import android.content.pm.ApplicationInfo;
 
+parcelable CompatibilityChangeConfig;
+
 /**
  * Platform private API for talking with the PlatformCompat service.
  *
@@ -125,4 +127,21 @@
      * @return {@code true} if the change is enabled for the current app.
      */
     boolean isChangeEnabledByUid(long changeId, int uid);
-}
\ No newline at end of file
+
+    /**
+     * Add overrides to compatibility changes.
+     *
+     * @param overrides Parcelable containing the compat change overrides to be applied.
+     * @param packageName The package name of the app whose changes will be overridden.
+     *
+     */
+    void setOverrides(in CompatibilityChangeConfig overrides, in String packageName);
+
+    /**
+     * Revert overrides to compatibility changes.
+     *
+     * @param packageName The package name of the app whose overrides will be cleared.
+     *
+     */
+    void clearOverrides(in String packageName);
+}
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index cdb79ab..f5708a5c 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -17,6 +17,7 @@
 package com.android.internal.content;
 
 import android.annotation.CallSuper;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Intent;
@@ -552,6 +553,11 @@
                     flags |= Document.FLAG_SUPPORTS_DELETE;
                     flags |= Document.FLAG_SUPPORTS_RENAME;
                     flags |= Document.FLAG_SUPPORTS_MOVE;
+
+                    if (shouldBlockFromTree(docId)) {
+                        flags |= Document.FLAG_DIR_BLOCKS_TREE;
+                    }
+
                 } else {
                     flags |= Document.FLAG_SUPPORTS_WRITE;
                     flags |= Document.FLAG_SUPPORTS_DELETE;
@@ -592,6 +598,10 @@
         return row;
     }
 
+    protected boolean shouldBlockFromTree(@NonNull String docId) {
+        return false;
+    }
+
     protected boolean typeSupportsMetadata(String mimeType) {
         return MetadataReader.isSupportedMimeType(mimeType)
                 || Document.MIME_TYPE_DIR.equals(mimeType);
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/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index a845b58..659134a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.statusbar;
 
 import android.app.Notification;
+import android.net.Uri;
 import android.content.ComponentName;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -77,6 +78,7 @@
     void onNotificationSettingsViewed(String key);
     void setSystemUiVisibility(int displayId, int vis, int mask, String cause);
     void onNotificationBubbleChanged(String key, boolean isBubble);
+    void grantInlineReplyUriPermission(String key, in Uri uri);
 
     void onGlobalActionsShown();
     void onGlobalActionsHidden();
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 0e078dd..7e1f13a 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -80,6 +80,10 @@
     }
 
     @Override
+    public void hideInsets(@InsetType int types, boolean fromIme)  throws RemoteException {
+    }
+
+    @Override
     public void moved(int newX, int newY) {
     }
 
diff --git a/core/java/com/android/internal/widget/LockPatternChecker.java b/core/java/com/android/internal/widget/LockPatternChecker.java
index 09bc28c..85a45fd 100644
--- a/core/java/com/android/internal/widget/LockPatternChecker.java
+++ b/core/java/com/android/internal/widget/LockPatternChecker.java
@@ -1,13 +1,9 @@
 package com.android.internal.widget;
 
-import android.annotation.UnsupportedAppUsage;
 import android.os.AsyncTask;
 
 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Helper class to check/verify PIN/Password/Pattern asynchronously.
  */
@@ -53,34 +49,28 @@
     }
 
     /**
-     * Verify a pattern asynchronously.
+     * Verify a lockscreen credential asynchronously.
      *
      * @param utils The LockPatternUtils instance to use.
-     * @param pattern The pattern to check.
-     * @param challenge The challenge to verify against the pattern.
-     * @param userId The user to check against the pattern.
+     * @param credential The credential to check.
+     * @param challenge The challenge to verify against the credential.
+     * @param userId The user to check against the credential.
      * @param callback The callback to be invoked with the verification result.
      */
-    public static AsyncTask<?, ?, ?> verifyPattern(final LockPatternUtils utils,
-            final List<LockPatternView.Cell> pattern,
+    public static AsyncTask<?, ?, ?> verifyCredential(final LockPatternUtils utils,
+            final LockscreenCredential credential,
             final long challenge,
             final int userId,
             final OnVerifyCallback callback) {
+        // Create a copy of the credential since checking credential is asynchrounous.
+        final LockscreenCredential credentialCopy = credential.duplicate();
         AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
             private int mThrottleTimeout;
-            private List<LockPatternView.Cell> patternCopy;
-
-            @Override
-            protected void onPreExecute() {
-                // Make a copy of the pattern to prevent race conditions.
-                // No need to clone the individual cells because they are immutable.
-                patternCopy = new ArrayList(pattern);
-            }
 
             @Override
             protected byte[] doInBackground(Void... args) {
                 try {
-                    return utils.verifyPattern(patternCopy, challenge, userId);
+                    return utils.verifyCredential(credentialCopy, challenge, userId);
                 } catch (RequestThrottledException ex) {
                     mThrottleTimeout = ex.getTimeoutMs();
                     return null;
@@ -90,6 +80,12 @@
             @Override
             protected void onPostExecute(byte[] result) {
                 callback.onVerified(result, mThrottleTimeout);
+                credentialCopy.zeroize();
+            }
+
+            @Override
+            protected void onCancelled() {
+                credentialCopy.zeroize();
             }
         };
         task.execute();
@@ -97,32 +93,26 @@
     }
 
     /**
-     * Checks a pattern asynchronously.
+     * Checks a lockscreen credential asynchronously.
      *
      * @param utils The LockPatternUtils instance to use.
-     * @param pattern The pattern to check.
-     * @param userId The user to check against the pattern.
+     * @param credential The credential to check.
+     * @param userId The user to check against the credential.
      * @param callback The callback to be invoked with the check result.
      */
-    public static AsyncTask<?, ?, ?> checkPattern(final LockPatternUtils utils,
-            final List<LockPatternView.Cell> pattern,
+    public static AsyncTask<?, ?, ?> checkCredential(final LockPatternUtils utils,
+            final LockscreenCredential credential,
             final int userId,
             final OnCheckCallback callback) {
+        // Create a copy of the credential since checking credential is asynchrounous.
+        final LockscreenCredential credentialCopy = credential.duplicate();
         AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
             private int mThrottleTimeout;
-            private List<LockPatternView.Cell> patternCopy;
-
-            @Override
-            protected void onPreExecute() {
-                // Make a copy of the pattern to prevent race conditions.
-                // No need to clone the individual cells because they are immutable.
-                patternCopy = new ArrayList(pattern);
-            }
 
             @Override
             protected Boolean doInBackground(Void... args) {
                 try {
-                    return utils.checkPattern(patternCopy, userId, callback::onEarlyMatched);
+                    return utils.checkCredential(credentialCopy, userId, callback::onEarlyMatched);
                 } catch (RequestThrottledException ex) {
                     mThrottleTimeout = ex.getTimeoutMs();
                     return false;
@@ -132,11 +122,13 @@
             @Override
             protected void onPostExecute(Boolean result) {
                 callback.onChecked(result, mThrottleTimeout);
+                credentialCopy.zeroize();
             }
 
             @Override
             protected void onCancelled() {
                 callback.onCancelled();
+                credentialCopy.zeroize();
             }
         };
         task.execute();
@@ -144,84 +136,29 @@
     }
 
     /**
-     * Verify a password asynchronously.
+     * Perform a lockscreen credential verification explicitly on a managed profile with unified
+     * challenge, using the parent user's credential.
      *
      * @param utils The LockPatternUtils instance to use.
-     * @param password The password to check.
-     * @param challenge The challenge to verify against the pattern.
-     * @param userId The user to check against the pattern.
-     * @param callback The callback to be invoked with the verification result.
-     *
-     * @deprecated Pass the password as a byte array.
-     */
-    @Deprecated
-    public static AsyncTask<?, ?, ?> verifyPassword(final LockPatternUtils utils,
-            final String password,
-            final long challenge,
-            final int userId,
-            final OnVerifyCallback callback) {
-        byte[] passwordBytes = password != null ? password.getBytes() : null;
-        return verifyPassword(utils, passwordBytes, challenge, userId, callback);
-    }
-
-    /**
-     * Verify a password asynchronously.
-     *
-     * @param utils The LockPatternUtils instance to use.
-     * @param password The password to check.
-     * @param challenge The challenge to verify against the pattern.
-     * @param userId The user to check against the pattern.
-     * @param callback The callback to be invoked with the verification result.
-     */
-    public static AsyncTask<?, ?, ?> verifyPassword(final LockPatternUtils utils,
-            final byte[] password,
-            final long challenge,
-            final int userId,
-            final OnVerifyCallback callback) {
-        AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
-            private int mThrottleTimeout;
-
-            @Override
-            protected byte[] doInBackground(Void... args) {
-                try {
-                    return utils.verifyPassword(password, challenge, userId);
-                } catch (RequestThrottledException ex) {
-                    mThrottleTimeout = ex.getTimeoutMs();
-                    return null;
-                }
-            }
-
-            @Override
-            protected void onPostExecute(byte[] result) {
-                callback.onVerified(result, mThrottleTimeout);
-            }
-        };
-        task.execute();
-        return task;
-    }
-
-    /**
-     * Verify a password asynchronously.
-     *
-     * @param utils The LockPatternUtils instance to use.
-     * @param password The password to check.
-     * @param challenge The challenge to verify against the pattern.
-     * @param userId The user to check against the pattern.
+     * @param credential The credential to check.
+     * @param challenge The challenge to verify against the credential.
+     * @param userId The user to check against the credential.
      * @param callback The callback to be invoked with the verification result.
      */
     public static AsyncTask<?, ?, ?> verifyTiedProfileChallenge(final LockPatternUtils utils,
-            final byte[] password,
-            final boolean isPattern,
+            final LockscreenCredential credential,
             final long challenge,
             final int userId,
             final OnVerifyCallback callback) {
+        // Create a copy of the credential since checking credential is asynchrounous.
+        final LockscreenCredential credentialCopy = credential.duplicate();
         AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
             private int mThrottleTimeout;
 
             @Override
             protected byte[] doInBackground(Void... args) {
                 try {
-                    return utils.verifyTiedProfileChallenge(password, isPattern, challenge, userId);
+                    return utils.verifyTiedProfileChallenge(credentialCopy, challenge, userId);
                 } catch (RequestThrottledException ex) {
                     mThrottleTimeout = ex.getTimeoutMs();
                     return null;
@@ -231,64 +168,12 @@
             @Override
             protected void onPostExecute(byte[] result) {
                 callback.onVerified(result, mThrottleTimeout);
-            }
-        };
-        task.execute();
-        return task;
-    }
-
-    /**
-     * Checks a password asynchronously.
-     *
-     * @param utils The LockPatternUtils instance to use.
-     * @param password The password to check.
-     * @param userId The user to check against the pattern.
-     * @param callback The callback to be invoked with the check result.
-     * @deprecated Pass passwords as byte[]
-     */
-    @UnsupportedAppUsage
-    @Deprecated
-    public static AsyncTask<?, ?, ?> checkPassword(final LockPatternUtils utils,
-            final String password,
-            final int userId,
-            final OnCheckCallback callback) {
-        byte[] passwordBytes = password != null ? password.getBytes() : null;
-        return checkPassword(utils, passwordBytes, userId, callback);
-    }
-
-    /**
-     * Checks a password asynchronously.
-     *
-     * @param utils The LockPatternUtils instance to use.
-     * @param passwordBytes The password to check.
-     * @param userId The user to check against the pattern.
-     * @param callback The callback to be invoked with the check result.
-     */
-    public static AsyncTask<?, ?, ?> checkPassword(final LockPatternUtils utils,
-            final byte[] passwordBytes,
-            final int userId,
-            final OnCheckCallback callback) {
-        AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
-            private int mThrottleTimeout;
-
-            @Override
-            protected Boolean doInBackground(Void... args) {
-                try {
-                    return utils.checkPassword(passwordBytes, userId, callback::onEarlyMatched);
-                } catch (RequestThrottledException ex) {
-                    mThrottleTimeout = ex.getTimeoutMs();
-                    return false;
-                }
-            }
-
-            @Override
-            protected void onPostExecute(Boolean result) {
-                callback.onChecked(result, mThrottleTimeout);
+                credentialCopy.zeroize();
             }
 
             @Override
             protected void onCancelled() {
-                callback.onCancelled();
+                credentialCopy.zeroize();
             }
         };
         task.execute();
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 070121c..8fea703 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -26,6 +26,7 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.app.admin.DevicePolicyManager;
@@ -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);
     }
@@ -365,11 +366,24 @@
                 null /* componentName */, userId);
     }
 
-    private byte[] verifyCredential(byte[] credential, int type, long challenge, int userId)
-            throws RequestThrottledException {
+    /**
+     * Check to see if a credential matches the saved one.
+     * If credential matches, return an opaque attestation that the challenge was verified.
+     *
+     * @param credential The credential to check.
+     * @param challenge The challenge to verify against the credential
+     * @return the attestation that the challenge was verified, or null
+     * @param userId The user whose credential is being verified
+     * @throws RequestThrottledException if credential verification is being throttled due to
+     *         to many incorrect attempts.
+     * @throws IllegalStateException if called on the main thread.
+     */
+    public byte[] verifyCredential(@NonNull LockscreenCredential credential, long challenge,
+            int userId) throws RequestThrottledException {
+        throwIfCalledOnMainThread();
         try {
-            VerifyCredentialResponse response = getLockSettings().verifyCredential(credential,
-                    type, challenge, userId);
+            VerifyCredentialResponse response = getLockSettings().verifyCredential(
+                    credential.getCredential(), credential.getType(), challenge, userId);
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
                 return response.getPayload();
             } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
@@ -382,11 +396,24 @@
         }
     }
 
-    private boolean checkCredential(byte[] credential, int type, int userId,
+    /**
+     * Check to see if a credential matches the saved one.
+     *
+     * @param credential The credential to check.
+     * @param userId The user whose credential is being checked
+     * @param progressCallback callback to deliver early signal that the credential matches
+     * @return {@code true} if credential matches, {@code false} otherwise
+     * @throws RequestThrottledException if credential verification is being throttled due to
+     *         to many incorrect attempts.
+     * @throws IllegalStateException if called on the main thread.
+     */
+    public boolean checkCredential(@NonNull LockscreenCredential credential, int userId,
             @Nullable CheckCredentialProgressCallback progressCallback)
             throws RequestThrottledException {
+        throwIfCalledOnMainThread();
         try {
-            VerifyCredentialResponse response = getLockSettings().checkCredential(credential, type,
+            VerifyCredentialResponse response = getLockSettings().checkCredential(
+                    credential.getCredential(), credential.getType(),
                     userId, wrapCallback(progressCallback));
 
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
@@ -402,79 +429,26 @@
     }
 
     /**
-     * Check to see if a pattern matches the saved pattern.
-     * If pattern matches, return an opaque attestation that the challenge
-     * was verified.
+     * Check if the credential of a managed profile with unified challenge matches. In this context,
+     * The credential should be the parent user's lockscreen password. If credential matches,
+     * return an opaque attestation associated with the managed profile that the challenge was
+     * verified.
      *
-     * @param pattern The pattern to check.
-     * @param challenge The challenge to verify against the pattern
-     * @return the attestation that the challenge was verified, or null.
+     * @param credential The parent user's credential to check.
+     * @param challenge The challenge to verify against the credential
+     * @return the attestation that the challenge was verified, or null
+     * @param userId The managed profile user id
+     * @throws RequestThrottledException if credential verification is being throttled due to
+     *         to many incorrect attempts.
+     * @throws IllegalStateException if called on the main thread.
      */
-    public byte[] verifyPattern(List<LockPatternView.Cell> pattern, long challenge, int userId)
-            throws RequestThrottledException {
-        throwIfCalledOnMainThread();
-        return verifyCredential(patternToByteArray(pattern), CREDENTIAL_TYPE_PATTERN, challenge,
-                userId);
-    }
-
-    /**
-     * Check to see if a pattern matches the saved pattern.  If no pattern exists,
-     * always returns true.
-     * @param pattern The pattern to check.
-     * @return Whether the pattern matches the stored one.
-     */
-    public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId)
-            throws RequestThrottledException {
-        return checkPattern(pattern, userId, null /* progressCallback */);
-    }
-
-    /**
-     * Check to see if a pattern matches the saved pattern.  If no pattern exists,
-     * always returns true.
-     * @param pattern The pattern to check.
-     * @return Whether the pattern matches the stored one.
-     */
-    public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId,
-            @Nullable CheckCredentialProgressCallback progressCallback)
-            throws RequestThrottledException {
-        throwIfCalledOnMainThread();
-        return checkCredential(patternToByteArray(pattern), CREDENTIAL_TYPE_PATTERN, userId,
-                progressCallback);
-    }
-
-    /**
-     * Check to see if a password matches the saved password.
-     * If password matches, return an opaque attestation that the challenge
-     * was verified.
-     *
-     * @param password The password to check.
-     * @param challenge The challenge to verify against the password
-     * @return the attestation that the challenge was verified, or null.
-     */
-    public byte[] verifyPassword(byte[] password, long challenge, int userId)
-            throws RequestThrottledException {
-        throwIfCalledOnMainThread();
-        return verifyCredential(password, CREDENTIAL_TYPE_PASSWORD, challenge, userId);
-    }
-
-
-    /**
-     * Check to see if a password matches the saved password.
-     * If password matches, return an opaque attestation that the challenge
-     * was verified.
-     *
-     * @param password The password to check.
-     * @param challenge The challenge to verify against the password
-     * @return the attestation that the challenge was verified, or null.
-     */
-    public byte[] verifyTiedProfileChallenge(byte[] password, boolean isPattern, long challenge,
-            int userId) throws RequestThrottledException {
+    public byte[] verifyTiedProfileChallenge(@NonNull LockscreenCredential credential,
+            long challenge, int userId) throws RequestThrottledException {
         throwIfCalledOnMainThread();
         try {
             VerifyCredentialResponse response =
-                    getLockSettings().verifyTiedProfileChallenge(password,
-                            isPattern ? CREDENTIAL_TYPE_PATTERN : CREDENTIAL_TYPE_PASSWORD, challenge,
-                            userId);
+                    getLockSettings().verifyTiedProfileChallenge(
+                            credential.getCredential(), credential.getType(), challenge, userId);
 
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
                 return response.getPayload();
@@ -489,61 +463,6 @@
     }
 
     /**
-     *
-     * Check to see if a password matches the saved password.  If no password exists,
-     * always returns true.
-     * @param password The password to check.
-     * @return Whether the password matches the stored one.
-     */
-    @UnsupportedAppUsage
-    public boolean checkPassword(String password, int userId) throws RequestThrottledException {
-        byte[] passwordBytes = password != null ? password.getBytes() : null;
-        return checkPassword(passwordBytes, userId, null /* progressCallback */);
-    }
-
-
-    /**
-     *
-     * Check to see if a password matches the saved password.  If no password exists,
-     * always returns true.
-     * @param password The password to check.
-     * @return Whether the password matches the stored one.
-     */
-    public boolean checkPassword(byte[] password, int userId) throws RequestThrottledException {
-        return checkPassword(password, userId, null /* progressCallback */);
-    }
-
-    // TODO(b/120484642): This method is necessary for vendor/qcom code and is a hidden api
-    /* *
-     * Check to see if a password matches the saved password.  If no password exists,
-     * always returns true.
-     * @param password The password to check.
-     * @return Whether the password matches the stored one.
-     */
-    public boolean checkPassword(String password, int userId,
-            @Nullable CheckCredentialProgressCallback progressCallback)
-            throws RequestThrottledException {
-        byte[] passwordBytes = password != null ? password.getBytes() : null;
-        throwIfCalledOnMainThread();
-        return checkCredential(passwordBytes, CREDENTIAL_TYPE_PASSWORD, userId, progressCallback);
-
-    }
-
-    /**
-     * Check to see if a password matches the saved password.  If no password exists,
-     * always returns true.
-     * @param password The password to check.
-     * @return Whether the password matches the stored one.
-     */
-
-    public boolean checkPassword(byte[] password, int userId,
-            @Nullable CheckCredentialProgressCallback progressCallback)
-            throws RequestThrottledException {
-        throwIfCalledOnMainThread();
-        return checkCredential(password, CREDENTIAL_TYPE_PASSWORD, userId, progressCallback);
-    }
-
-    /**
      * Check to see if vold already has the password.
      * Note that this also clears vold's copy of the password.
      * @return Whether the vold password matches or not.
@@ -560,9 +479,10 @@
      * Returns the password history hash factor, needed to check new password against password
      * history with {@link #checkPasswordHistory(byte[], byte[], int)}
      */
-    public byte[] getPasswordHistoryHashFactor(byte[] currentPassword, int userId) {
+    public byte[] getPasswordHistoryHashFactor(@NonNull LockscreenCredential currentPassword,
+            int userId) {
         try {
-            return getLockSettings().getHashFactor(currentPassword, userId);
+            return getLockSettings().getHashFactor(currentPassword.getCredential(), userId);
         } catch (RemoteException e) {
             Log.e(TAG, "failed to get hash factor", e);
             return null;
@@ -679,57 +599,6 @@
     }
 
     /**
-     * Clear any lock pattern or password.
-
-     * <p> This method will fail (returning {@code false}) if the previously
-     * saved password provided is incorrect, or if the lockscreen verification
-     * is still being throttled.
-     *
-     * @param savedCredential The previously saved credential
-     * @param userHandle the user whose pattern is to be saved.
-     * @return whether this was successful or not.
-     * @throws RuntimeException if password change encountered an unrecoverable error.
-     */
-    public boolean clearLock(byte[] savedCredential, int userHandle) {
-        return clearLock(savedCredential, userHandle, false);
-    }
-
-    /**
-     * Clear any lock pattern or password, with the option to ignore incorrect existing credential.
-     * <p> This method will fail (returning {@code false}) if the previously
-     * saved password provided is incorrect, or if the lockscreen verification
-     * is still being throttled.
-     *
-     * @param savedCredential The previously saved credential
-     * @param userHandle the user whose pattern is to be saved.
-     * @return whether this was successful or not.
-     * @throws RuntimeException if password change encountered an unrecoverable error.
-     */
-    public boolean clearLock(byte[] savedCredential, int userHandle, boolean allowUntrustedChange) {
-        final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
-        setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED, userHandle);
-
-        try {
-            if (!getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE, savedCredential,
-                    PASSWORD_QUALITY_UNSPECIFIED, userHandle, allowUntrustedChange)) {
-                return false;
-            }
-        } catch (RemoteException | RuntimeException e) {
-            setKeyguardStoredPasswordQuality(currentQuality, userHandle);
-            throw new RuntimeException("Failed to clear lock", e);
-        }
-
-        if (userHandle == UserHandle.USER_SYSTEM) {
-            // Set the encryption password to default.
-            updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
-            setCredentialRequiredToDecrypt(false);
-        }
-
-        onAfterChangingPassword(userHandle);
-        return true;
-    }
-
-    /**
      * Disable showing lock screen at all for a given user.
      * This is only meaningful if pattern, pin or password are not set.
      *
@@ -762,75 +631,88 @@
                 || isDemoUser;
     }
 
-    /**
-     * Save a lock pattern.
-     *
-     * <p> This method will fail (returning {@code false}) if the previously saved pattern provided
-     * is incorrect, or if the lockscreen verification is still being throttled.
-     *
-     * @param pattern The new pattern to save.
-     * @param savedPattern The previously saved pattern, converted to byte[] format
-     * @param userId the user whose pattern is to be saved.
-     * @return whether this was successful or not.
-     * @throws RuntimeException if password change encountered an unrecoverable error.
-     */
-    public boolean saveLockPattern(List<LockPatternView.Cell> pattern, byte[] savedPattern,
-            int userId) {
-        return saveLockPattern(pattern, savedPattern, userId, false);
+    /** Returns if the given quality maps to an alphabetic password */
+    public static boolean isQualityAlphabeticPassword(int quality) {
+        return quality >= PASSWORD_QUALITY_ALPHABETIC;
+    }
+
+    /** Returns if the given quality maps to an numeric pin */
+    public static boolean isQualityNumericPin(int quality) {
+        return quality == PASSWORD_QUALITY_NUMERIC || quality == PASSWORD_QUALITY_NUMERIC_COMPLEX;
     }
 
     /**
-     * Save a lock pattern.
+     * Save a new lockscreen credential.
      *
-     * <p> This method will fail (returning {@code false}) if the previously saved pattern provided
-     * is incorrect, or if the lockscreen verification is still being throttled.
+     * <p> This method will fail (returning {@code false}) if the previously saved credential
+     * provided is incorrect, or if the lockscreen verification is still being throttled.
      *
-     * @param pattern The new pattern to save.
-     * @param savedPattern The previously saved pattern, converted to byte[] format
-     * @param userId the user whose pattern is to be saved.
-     * @param allowUntrustedChange whether we want to allow saving a new password if the existing
-     * password being provided is incorrect.
-     * @return whether this was successful or not.
+     * @param newCredential The new credential to save
+     * @param savedCredential The current credential
+     * @param userId the user whose lockscreen credential is to be changed
+     *
+     * @return whether this method saved the new password successfully or not. This flow will fail
+     * and return false if the given credential is wrong.
      * @throws RuntimeException if password change encountered an unrecoverable error.
      */
-    public boolean saveLockPattern(List<LockPatternView.Cell> pattern, byte[] savedPattern,
-            int userId, boolean allowUntrustedChange) {
+    public boolean setLockCredential(@NonNull LockscreenCredential newCredential,
+            @NonNull LockscreenCredential savedCredential, int userId) {
+        return setLockCredential(newCredential, savedCredential, userId, false);
+    }
+
+    /**
+     * Save a new lockscreen credential.
+     * <p> This method will fail (returning {@code false}) if the previously saved pattern provided
+     * is incorrect and allowUntrustedChange is false, or if the lockscreen verification is still
+     * being throttled.
+     * @param newCredential The new credential to save
+     * @param savedCredential The current credential
+     * @param userHandle the user whose lockscreen credential is to be changed
+     * @param allowUntrustedChange whether we want to allow saving a new pattern if the existing
+     * credentialt being provided is incorrect.
+     *
+     * @return whether this method saved the new password successfully or not. This flow will fail
+     * and return false if the given credential is wrong and allowUntrustedChange is false.
+     * @throws RuntimeException if password change encountered an unrecoverable error.
+     */
+    public boolean setLockCredential(@NonNull LockscreenCredential newCredential,
+            @NonNull LockscreenCredential savedCredential, int userHandle,
+            boolean allowUntrustedChange) {
         if (!hasSecureLockScreen()) {
             throw new UnsupportedOperationException(
                     "This operation requires the lock screen feature.");
         }
-        if (pattern == null || pattern.size() < MIN_LOCK_PATTERN_SIZE) {
-            throw new IllegalArgumentException("pattern must not be null and at least "
-                    + MIN_LOCK_PATTERN_SIZE + " dots long.");
-        }
+        newCredential.checkLength();
 
-        final byte[] bytePattern = patternToByteArray(pattern);
-        final int currentQuality = getKeyguardStoredPasswordQuality(userId);
-        setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_SOMETHING, userId);
+        final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
+        setKeyguardStoredPasswordQuality(newCredential.getQuality(), userHandle);
+
         try {
-            if (!getLockSettings().setLockCredential(bytePattern, CREDENTIAL_TYPE_PATTERN,
-                    savedPattern, PASSWORD_QUALITY_SOMETHING, userId, allowUntrustedChange)) {
+            if (!getLockSettings().setLockCredential(
+                    newCredential.getCredential(), newCredential.getType(),
+                    savedCredential.getCredential(),
+                    newCredential.getQuality(), userHandle, allowUntrustedChange)) {
+                setKeyguardStoredPasswordQuality(currentQuality, userHandle);
                 return false;
             }
         } catch (RemoteException | RuntimeException e) {
-            setKeyguardStoredPasswordQuality(currentQuality, userId);
-            throw new RuntimeException("Couldn't save lock pattern", e);
-        }
-        // Update the device encryption password.
-        if (userId == UserHandle.USER_SYSTEM
-                && LockPatternUtils.isDeviceEncryptionEnabled()) {
-            if (!shouldEncryptWithCredentials(true)) {
-                clearEncryptionPassword();
-            } else {
-                updateEncryptionPassword(StorageManager.CRYPT_TYPE_PATTERN, bytePattern);
-            }
+            setKeyguardStoredPasswordQuality(currentQuality, userHandle);
+            throw new RuntimeException("Unable to save lock password", e);
         }
 
-        reportPatternWasChosen(userId);
-        onAfterChangingPassword(userId);
+        onPostPasswordChanged(newCredential, userHandle);
         return true;
     }
 
+    private void onPostPasswordChanged(LockscreenCredential newCredential, int userHandle) {
+        updateEncryptionPasswordIfNeeded(newCredential, userHandle);
+        if (newCredential.isPattern()) {
+            reportPatternWasChosen(userHandle);
+        }
+        updatePasswordHistory(newCredential, userHandle);
+        reportEnabledTrustAgentsChanged(userHandle);
+    }
+
     private void updateCryptoUserInfo(int userId) {
         if (userId != UserHandle.USER_SYSTEM) {
             return;
@@ -929,149 +811,35 @@
     }
 
     /**
-     * Save a lock password.  Does not ensure that the password is as good
-     * as the requested mode, but will adjust the mode to be as good as the
-     * password.
-     *
-     * <p> This method will fail (returning {@code false}) if the previously
-     * saved password provided is incorrect, or if the lockscreen verification
-     * is still being throttled.
-     *
-     * @param password The password to save
-     * @param savedPassword The previously saved lock password, or null if none
-     * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
-     * android.content.ComponentName)}
-     * @param userHandle The userId of the user to change the password for
-     * @return whether this was successful or not.
-     * @throws RuntimeException if password change encountered an unrecoverable error.
-     * @deprecated Pass password as a byte array
-     */
-    @Deprecated
-    public boolean saveLockPassword(String password, String savedPassword, int requestedQuality,
-            int userHandle) {
-        byte[] passwordBytes = password != null ? password.getBytes() : null;
-        byte[] savedPasswordBytes = savedPassword != null ? savedPassword.getBytes() : null;
-        return saveLockPassword(passwordBytes, savedPasswordBytes, requestedQuality, userHandle);
-    }
-
-    /**
-     * Save a lock password.  Does not ensure that the password is as good
-     * as the requested mode, but will adjust the mode to be as good as the
-     * password.
-     *
-     * <p> This method will fail (returning {@code false}) if the previously
-     * saved password provided is incorrect, or if the lockscreen verification
-     * is still being throttled.
-     *
-     * @param password The password to save
-     * @param savedPassword The previously saved lock password, or null if none
-     * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
-     * android.content.ComponentName)}
-     * @param userHandle The userId of the user to change the password for
-     * @return whether this was successful or not.
-     * @throws RuntimeException if password change encountered an unrecoverable error.
-     */
-    public boolean saveLockPassword(byte[] password, byte[] savedPassword, int requestedQuality,
-            int userHandle) {
-        return saveLockPassword(password, savedPassword, requestedQuality,
-                userHandle, false);
-    }
-
-    /**
-     * Save a lock password.  Does not ensure that the password is as good
-     * as the requested mode, but will adjust the mode to be as good as the
-     * password.
-     *
-     * <p> This method will fail (returning {@code false}) if the previously
-     * saved password provided is incorrect, or if the lockscreen verification
-     * is still being throttled.
-     *
-     * @param password The password to save
-     * @param savedPassword The previously saved lock password, or null if none
-     * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
-     * android.content.ComponentName)}
-     * @param userHandle The userId of the user to change the password for
-     * @param allowUntrustedChange whether we want to allow saving a new password if the existing
-     * password being provided is incorrect.
-     * @return whether this method saved the new password successfully or not. This flow will fail
-     * and return false if the given credential is wrong and allowUntrustedChange is false.
-     * @throws RuntimeException if password change encountered an unrecoverable error.
-     */
-    public boolean saveLockPassword(byte[] password, byte[] savedPassword,
-            int requestedQuality, int userHandle, boolean allowUntrustedChange) {
-        if (!hasSecureLockScreen()) {
-            throw new UnsupportedOperationException(
-                    "This operation requires the lock screen feature.");
-        }
-        if (password == null || password.length < MIN_LOCK_PASSWORD_SIZE) {
-            throw new IllegalArgumentException("password must not be null and at least "
-                    + "of length " + MIN_LOCK_PASSWORD_SIZE);
-        }
-
-        if (requestedQuality < PASSWORD_QUALITY_NUMERIC) {
-            throw new IllegalArgumentException("quality must be at least NUMERIC, but was "
-                    + requestedQuality);
-        }
-
-        final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
-        final int passwordQuality = PasswordMetrics.computeForPassword(password).quality;
-        final int newKeyguardQuality =
-                computeKeyguardQuality(CREDENTIAL_TYPE_PASSWORD, requestedQuality, passwordQuality);
-        setKeyguardStoredPasswordQuality(newKeyguardQuality, userHandle);
-        try {
-            getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword,
-                    requestedQuality, userHandle, allowUntrustedChange);
-        } catch (RemoteException | RuntimeException e) {
-            setKeyguardStoredPasswordQuality(currentQuality, userHandle);
-            throw new RuntimeException("Unable to save lock password", e);
-        }
-
-        updateEncryptionPasswordIfNeeded(password, passwordQuality, userHandle);
-        updatePasswordHistory(password, userHandle);
-        onAfterChangingPassword(userHandle);
-        return true;
-    }
-
-    /**
-     * Compute keyguard credential quality to store in PASSWORD_TYPE_KEY by computing max between
-     * them so that digit-only password is distinguished from PIN.
-     *
-     * TODO: remove this method and make CREDENTIAL_TYPE distinguish between PIN and password, so
-     * that this quality is no longer needs to be persisted.
-     */
-    private int computeKeyguardQuality(
-            @CredentialType int credentialType, int requestedQuality, int passwordQuality) {
-        return credentialType == CREDENTIAL_TYPE_PASSWORD
-                ? Math.max(passwordQuality, requestedQuality) : passwordQuality;
-    }
-
-    /**
      * Update device encryption password if calling user is USER_SYSTEM and device supports
      * encryption.
      */
-    private void updateEncryptionPasswordIfNeeded(byte[] password, int quality, int userHandle) {
+    private void updateEncryptionPasswordIfNeeded(LockscreenCredential credential, int userHandle) {
         // Update the device encryption password.
-        if (userHandle == UserHandle.USER_SYSTEM
-                && LockPatternUtils.isDeviceEncryptionEnabled()) {
-            if (!shouldEncryptWithCredentials(true)) {
-                clearEncryptionPassword();
-            } else {
-                boolean numeric = quality == PASSWORD_QUALITY_NUMERIC;
-                boolean numericComplex = quality == PASSWORD_QUALITY_NUMERIC_COMPLEX;
-                int type = numeric || numericComplex ? StorageManager.CRYPT_TYPE_PIN
-                        : StorageManager.CRYPT_TYPE_PASSWORD;
-                updateEncryptionPassword(type, password);
-            }
+        if (userHandle != UserHandle.USER_SYSTEM || !isDeviceEncryptionEnabled()) {
+            return;
         }
+        if (!shouldEncryptWithCredentials(true)) {
+            updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
+            return;
+        }
+        if (credential.isNone()) {
+            // Set the encryption password to default.
+            setCredentialRequiredToDecrypt(false);
+        }
+        updateEncryptionPassword(credential.getStorageCryptType(), credential.getCredential());
     }
 
     /**
      * Store the hash of the *current* password in the password history list, if device policy
      * enforces password history requirement.
      */
-    private void updatePasswordHistory(byte[] password, int userHandle) {
-        if (password == null || password.length == 0) {
-            Log.e(TAG, "checkPasswordHistory: empty password");
+    private void updatePasswordHistory(LockscreenCredential password, int userHandle) {
+        if (password.isNone()) {
+            return;
+        }
+        if (password.isPattern()) {
+            // Do not keep track of historical patterns
             return;
         }
         // Add the password to the password history. We assume all
@@ -1085,10 +853,10 @@
             passwordHistory = "";
         } else {
             final byte[] hashFactor = getPasswordHistoryHashFactor(password, userHandle);
-            String hash = passwordToHistoryHash(password, hashFactor, userHandle);
+            String hash = passwordToHistoryHash(password.getCredential(), hashFactor, userHandle);
             if (hash == null) {
                 Log.e(TAG, "Compute new style password hash failed, fallback to legacy style");
-                hash = legacyPasswordToHash(password, userHandle);
+                hash = legacyPasswordToHash(password.getCredential(), userHandle);
             }
             if (TextUtils.isEmpty(passwordHistory)) {
                 passwordHistory = hash;
@@ -1132,8 +900,8 @@
     }
 
     /**
-     * Retrieves the quality mode for {@param userHandle}.
-     * {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
+     * Retrieves the quality mode for {@code userHandle}.
+     * @see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)
      *
      * @return stored password quality
      */
@@ -1147,37 +915,37 @@
     }
 
     /**
-     * Enables/disables the Separate Profile Challenge for this {@param userHandle}. This is a no-op
+     * Enables/disables the Separate Profile Challenge for this {@code userHandle}. This is a no-op
      * for user handles that do not belong to a managed profile.
      *
      * @param userHandle Managed profile user id
      * @param enabled True if separate challenge is enabled
-     * @param managedUserPassword Managed profile previous password. Null when {@param enabled} is
+     * @param managedUserPassword Managed profile previous password. Null when {@code enabled} is
      *            true
      */
     public void setSeparateProfileChallengeEnabled(int userHandle, boolean enabled,
-            byte[] managedUserPassword) {
+            LockscreenCredential managedUserPassword) {
         if (!isManagedProfile(userHandle)) {
             return;
         }
         try {
             getLockSettings().setSeparateProfileChallengeEnabled(userHandle, enabled,
-                    managedUserPassword);
-            onAfterChangingPassword(userHandle);
+                    managedUserPassword.getCredential());
+            reportEnabledTrustAgentsChanged(userHandle);
         } catch (RemoteException e) {
             Log.e(TAG, "Couldn't update work profile challenge enabled");
         }
     }
 
     /**
-     * Returns true if {@param userHandle} is a managed profile with separate challenge.
+     * Returns true if {@code userHandle} is a managed profile with separate challenge.
      */
     public boolean isSeparateProfileChallengeEnabled(int userHandle) {
         return isManagedProfile(userHandle) && hasSeparateChallenge(userHandle);
     }
 
     /**
-     * Returns true if {@param userHandle} is a managed profile with unified challenge.
+     * Returns true if {@code userHandle} is a managed profile with unified challenge.
      */
     public boolean isManagedProfileWithUnifiedChallenge(int userHandle) {
         return isManagedProfile(userHandle) && !hasSeparateChallenge(userHandle);
@@ -1218,20 +986,6 @@
 
     /**
      * Deserialize a pattern.
-     * @param string The pattern serialized with {@link #patternToString}
-     * @return The pattern.
-     * @deprecated Pass patterns as byte[] and use byteArrayToPattern
-     */
-    @Deprecated
-    public static List<LockPatternView.Cell> stringToPattern(String string) {
-        if (string == null) {
-            return null;
-        }
-        return byteArrayToPattern(string.getBytes());
-    }
-
-    /**
-     * Deserialize a pattern.
      * @param  bytes The pattern serialized with {@link #patternToByteArray}
      * @return The pattern.
      */
@@ -1252,19 +1006,6 @@
     /**
      * Serialize a pattern.
      * @param pattern The pattern.
-     * @return The pattern in string form.
-     * @deprecated Use patternToByteArray instead.
-     */
-    @UnsupportedAppUsage
-    @Deprecated
-    public static String patternToString(List<LockPatternView.Cell> pattern) {
-        return new String(patternToByteArray(pattern));
-    }
-
-
-    /**
-     * Serialize a pattern.
-     * @param pattern The pattern.
      * @return The pattern in byte array form.
      */
     public static byte[] patternToByteArray(List<LockPatternView.Cell> pattern) {
@@ -1281,34 +1022,6 @@
         return res;
     }
 
-    /*
-     * Generate an SHA-1 hash for the pattern. Not the most secure, but it is
-     * at least a second level of protection. First level is that the file
-     * is in a location only readable by the system process.
-     * @param pattern the gesture pattern.
-     * @return the hash of the pattern in a byte array.
-     */
-    @UnsupportedAppUsage
-    public static byte[] patternToHash(List<LockPatternView.Cell> pattern) {
-        if (pattern == null) {
-            return null;
-        }
-
-        final int patternSize = pattern.size();
-        byte[] res = new byte[patternSize];
-        for (int i = 0; i < patternSize; i++) {
-            LockPatternView.Cell cell = pattern.get(i);
-            res[i] = (byte) (cell.getRow() * 3 + cell.getColumn());
-        }
-        try {
-            MessageDigest md = MessageDigest.getInstance("SHA-1");
-            byte[] hash = md.digest(res);
-            return hash;
-        } catch (NoSuchAlgorithmException nsa) {
-            return res;
-        }
-    }
-
     private String getSalt(int userId) {
         long salt = getLong(LOCK_PASSWORD_SALT_KEY, 0, userId);
         if (salt == 0) {
@@ -1332,6 +1045,7 @@
      * @param password the gesture pattern.
      *
      * @return the hash of the pattern in a byte array.
+     * TODO: move to LockscreenCredential class
      */
     public String legacyPasswordToHash(byte[] password, int userId) {
         if (password == null || password.length == 0) {
@@ -1362,6 +1076,7 @@
 
     /**
      * Hash the password for password history check purpose.
+     * TODO: move to LockscreenCredential class
      */
     private String passwordToHistoryHash(byte[] passwordToHash, byte[] hashFactor, int userId) {
         if (passwordToHash == null || passwordToHash.length == 0 || hashFactor == null) {
@@ -1629,7 +1344,7 @@
     }
 
     /**
-     * Disable trust until credentials have been entered for user {@param userId}.
+     * Disable trust until credentials have been entered for user {@code userId}.
      *
      * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
      *
@@ -1640,7 +1355,7 @@
     }
 
     /**
-     * Requests strong authentication for user {@param userId}.
+     * Requests strong authentication for user {@code userId}.
      *
      * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
      *
@@ -1657,7 +1372,7 @@
         }
     }
 
-    private void onAfterChangingPassword(int userHandle) {
+    private void reportEnabledTrustAgentsChanged(int userHandle) {
         getTrustManager().reportEnabledTrustAgentsChanged(userHandle);
     }
 
@@ -1823,54 +1538,36 @@
      * <p>This method is only available to code running in the system server process itself.
      *
      * @param credential The new credential to be set
-     * @param type Credential type: password / pattern / none.
-     * @param requestedQuality the requested password quality by DevicePolicyManager.
-     *        See {@link DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
      * @param tokenHandle Handle of the escrow token
      * @param token Escrow token
-     * @param userId The user who's lock credential to be changed
+     * @param userHandle The user who's lock credential to be changed
      * @return {@code true} if the operation is successful.
      */
-    public boolean setLockCredentialWithToken(byte[] credential, int type, int requestedQuality,
-            long tokenHandle, byte[] token, int userId) {
+    public boolean setLockCredentialWithToken(LockscreenCredential credential, long tokenHandle,
+            byte[] token, int userHandle) {
         if (!hasSecureLockScreen()) {
             throw new UnsupportedOperationException(
                     "This operation requires the lock screen feature.");
         }
+        credential.checkLength();
         LockSettingsInternal localService = getLockSettingsInternal();
-        if (type != CREDENTIAL_TYPE_NONE) {
-            if (credential == null || credential.length < MIN_LOCK_PASSWORD_SIZE) {
-                throw new IllegalArgumentException("password must not be null and at least "
-                        + "of length " + MIN_LOCK_PASSWORD_SIZE);
-            }
-            final int quality = PasswordMetrics.computeForCredential(type, credential).quality;
-            final int keyguardQuality = computeKeyguardQuality(type, quality, requestedQuality);
-            if (!localService.setLockCredentialWithToken(credential, type, tokenHandle, token,
-                    keyguardQuality, userId)) {
+
+        final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
+        setKeyguardStoredPasswordQuality(credential.getQuality(), userHandle);
+
+        try {
+            if (!localService.setLockCredentialWithToken(credential.getCredential(),
+                    credential.getType(),
+                    tokenHandle, token, credential.getType(), userHandle)) {
+                setKeyguardStoredPasswordQuality(currentQuality, userHandle);
                 return false;
             }
-            setKeyguardStoredPasswordQuality(quality, userId);
-
-            updateEncryptionPasswordIfNeeded(credential, quality, userId);
-            updatePasswordHistory(credential, userId);
-            onAfterChangingPassword(userId);
-        } else {
-            if (!(credential == null || credential.length == 0)) {
-                throw new IllegalArgumentException("password must be emtpy for NONE type");
-            }
-            if (!localService.setLockCredentialWithToken(null, CREDENTIAL_TYPE_NONE, tokenHandle,
-                    token, PASSWORD_QUALITY_UNSPECIFIED, userId)) {
-                return false;
-            }
-            setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED, userId);
-
-            if (userId == UserHandle.USER_SYSTEM) {
-                // Set the encryption password to default.
-                updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
-                setCredentialRequiredToDecrypt(false);
-            }
+        } catch (RuntimeException e) {
+            setKeyguardStoredPasswordQuality(currentQuality, userHandle);
+            throw new RuntimeException("Unable to save lock credential", e);
         }
-        onAfterChangingPassword(userId);
+
+        onPostPasswordChanged(credential, userHandle);
         return true;
     }
 
@@ -1997,7 +1694,7 @@
         }
 
         /**
-         * @return true if unlocking with trust alone is allowed for {@param userId} by the current
+         * @return true if unlocking with trust alone is allowed for {@code userId} by the current
          * strong authentication requirements.
          */
         public boolean isTrustAllowedForUser(int userId) {
@@ -2005,7 +1702,7 @@
         }
 
         /**
-         * @return true if unlocking with a biometric method alone is allowed for {@param userId}
+         * @return true if unlocking with a biometric method alone is allowed for {@code userId}
          * by the current strong authentication requirements.
          */
         public boolean isBiometricAllowedForUser(int userId) {
@@ -2013,7 +1710,7 @@
         }
 
         /**
-         * Called when the strong authentication requirements for {@param userId} changed.
+         * Called when the strong authentication requirements for {@code userId} changed.
          */
         public void onStrongAuthRequiredChanged(int userId) {
         }
@@ -2102,22 +1799,4 @@
         return FRP_CREDENTIAL_ENABLED && context.getResources().getBoolean(
                 com.android.internal.R.bool.config_enableCredentialFactoryResetProtection);
     }
-
-    /**
-     * Converts a CharSequence to a byte array without requiring a toString(), which creates an
-     * additional copy.
-     *
-     * @param chars The CharSequence to convert
-     * @return A byte array representing the input
-     */
-    public static byte[] charSequenceToByteArray(CharSequence chars) {
-        if (chars == null) {
-            return null;
-        }
-        byte[] bytes = new byte[chars.length()];
-        for (int i = 0; i < chars.length(); i++) {
-            bytes[i] = (byte) chars.charAt(i);
-        }
-        return bytes;
-    }
 }
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 3f6c4d4..74a0aa3 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -1318,7 +1318,7 @@
         super.onRestoreInstanceState(ss.getSuperState());
         setPattern(
                 DisplayMode.Correct,
-                LockPatternUtils.stringToPattern(ss.getSerializedPattern()));
+                LockPatternUtils.byteArrayToPattern(ss.getSerializedPattern().getBytes()));
         mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()];
         mInputEnabled = ss.isInputEnabled();
         mInStealthMode = ss.isInStealthMode();
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
new file mode 100644
index 0000000..19e6d97
--- /dev/null
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -0,0 +1,339 @@
+/*
+ * 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;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A class representing a lockscreen credential. It can be either an empty password, a pattern
+ * or a password (or PIN).
+ *
+ * <p> As required by some security certification, the framework tries its best to
+ * remove copies of the lockscreen credential bytes from memory. In this regard, this class
+ * abuses the {@link AutoCloseable} interface for sanitizing memory. This
+ * presents a nice syntax to auto-zeroize memory with the try-with-resource statement:
+ * <pre>
+ * try {LockscreenCredential credential = LockscreenCredential.createPassword(...) {
+ *     // Process the credential in some way
+ * }
+ * </pre>
+ * With this construct, we can garantee that there will be no copies of the password left in
+ * memory when the credential goes out of scope. This should help mitigate certain class of
+ * attacks where the attcker gains read-only access to full device memory (cold boot attack,
+ * unsecured software/hardware memory dumping interfaces such as JTAG).
+ */
+public class LockscreenCredential implements Parcelable, AutoCloseable {
+
+    private final int mType;
+    // Stores raw credential bytes, or null if credential has been zeroized. An empty password
+    // is represented as a byte array of length 0.
+    private byte[] mCredential;
+    // Store the quality of the password, this is used to distinguish between pin
+    // (PASSWORD_QUALITY_NUMERIC) and password (PASSWORD_QUALITY_ALPHABETIC).
+    private final int mQuality;
+
+    /**
+     * Private constructor, use static builder methods instead.
+     *
+     * <p> Builder methods should create a private copy of the credential bytes and pass in here.
+     * LockscreenCredential will only store the reference internally without copying. This is to
+     * minimize the number of extra copies introduced.
+     */
+    private LockscreenCredential(int type, int quality, byte[] credential) {
+        Preconditions.checkNotNull(credential);
+        if (type == CREDENTIAL_TYPE_NONE) {
+            Preconditions.checkArgument(credential.length == 0);
+        } else {
+            Preconditions.checkArgument(credential.length > 0);
+        }
+        mType = type;
+        mQuality = quality;
+        mCredential = credential;
+    }
+
+    /**
+     * Creates a LockscreenCredential object representing empty password.
+     */
+    public static LockscreenCredential createNone() {
+        return new LockscreenCredential(CREDENTIAL_TYPE_NONE, PASSWORD_QUALITY_UNSPECIFIED,
+                new byte[0]);
+    }
+
+    /**
+     * Creates a LockscreenCredential object representing the given pattern.
+     */
+    public static LockscreenCredential createPattern(@NonNull List<LockPatternView.Cell> pattern) {
+        return new LockscreenCredential(CREDENTIAL_TYPE_PATTERN,
+                PASSWORD_QUALITY_SOMETHING,
+                LockPatternUtils.patternToByteArray(pattern));
+    }
+
+    /**
+     * Creates a LockscreenCredential object representing the given alphabetic password.
+     */
+    public static LockscreenCredential createPassword(@NonNull CharSequence password) {
+        return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
+                PASSWORD_QUALITY_ALPHABETIC,
+                charSequenceToByteArray(password));
+    }
+
+    /**
+     * Creates a LockscreenCredential object representing the given numeric PIN.
+     */
+    public static LockscreenCredential createPin(@NonNull CharSequence pin) {
+        return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
+                PASSWORD_QUALITY_NUMERIC,
+                charSequenceToByteArray(pin));
+    }
+
+    /**
+     * Creates a LockscreenCredential object representing the given alphabetic password.
+     * If the supplied password is empty, create an empty credential object.
+     */
+    public static LockscreenCredential createPasswordOrNone(@Nullable CharSequence password) {
+        if (TextUtils.isEmpty(password)) {
+            return createNone();
+        } else {
+            return createPassword(password);
+        }
+    }
+
+    /**
+     * Creates a LockscreenCredential object representing the given numeric PIN.
+     * If the supplied password is empty, create an empty credential object.
+     */
+    public static LockscreenCredential createPinOrNone(@Nullable CharSequence pin) {
+        if (TextUtils.isEmpty(pin)) {
+            return createNone();
+        } else {
+            return createPin(pin);
+        }
+    }
+
+    /**
+     * Create a LockscreenCredential object based on raw credential and type
+     * TODO: Remove once LSS.setUserPasswordMetrics accepts a LockscreenCredential
+     */
+    public static LockscreenCredential createRaw(int type, byte[] credential) {
+        if (type == CREDENTIAL_TYPE_NONE) {
+            return createNone();
+        } else {
+            return new LockscreenCredential(type, PASSWORD_QUALITY_UNSPECIFIED, credential);
+        }
+    }
+
+    private void ensureNotZeroized() {
+        Preconditions.checkState(mCredential != null, "Credential is already zeroized");
+    }
+    /**
+     * Returns the type of this credential. Can be one of {@link #CREDENTIAL_TYPE_NONE},
+     * {@link #CREDENTIAL_TYPE_PATTERN} or {@link #CREDENTIAL_TYPE_PASSWORD}.
+     *
+     * TODO: Remove once credential type is internal. Callers should use {@link #isNone},
+     * {@link #isPattern} and {@link #isPassword} instead.
+     */
+    public int getType() {
+        ensureNotZeroized();
+        return mType;
+    }
+
+    /**
+     * Returns the quality type of the credential
+     */
+    public int getQuality() {
+        ensureNotZeroized();
+        return mQuality;
+    }
+
+    /**
+     * Returns the credential bytes. This is a direct reference of the internal field so
+     * callers should not modify it.
+     *
+     */
+    public byte[] getCredential() {
+        ensureNotZeroized();
+        return mCredential;
+    }
+
+    /**
+     *  Returns the credential type recognized by {@link StorageManager}. Can be one of
+     *  {@link StorageManager#CRYPT_TYPE_DEFAULT}, {@link StorageManager#CRYPT_TYPE_PATTERN},
+     *  {@link StorageManager#CRYPT_TYPE_PIN} or {@link StorageManager#CRYPT_TYPE_PASSWORD}.
+     */
+    public int getStorageCryptType() {
+        if (isNone()) {
+            return StorageManager.CRYPT_TYPE_DEFAULT;
+        }
+        if (isPattern()) {
+            return StorageManager.CRYPT_TYPE_PATTERN;
+        }
+        if (isPassword()) {
+            return mQuality == PASSWORD_QUALITY_NUMERIC
+                    ? StorageManager.CRYPT_TYPE_PIN : StorageManager.CRYPT_TYPE_PASSWORD;
+        }
+        throw new IllegalStateException("Unhandled credential type");
+    }
+
+    /** Returns whether this is an empty credential */
+    public boolean isNone() {
+        ensureNotZeroized();
+        return mType == CREDENTIAL_TYPE_NONE;
+    }
+
+    /** Returns whether this is a pattern credential */
+    public boolean isPattern() {
+        ensureNotZeroized();
+        return mType == CREDENTIAL_TYPE_PATTERN;
+    }
+
+    /** Returns whether this is a password credential */
+    public boolean isPassword() {
+        ensureNotZeroized();
+        return mType == CREDENTIAL_TYPE_PASSWORD;
+    }
+
+    /** Returns the length of the credential */
+    public int size() {
+        ensureNotZeroized();
+        return mCredential.length;
+    }
+
+    /** Create a copy of the credential */
+    public LockscreenCredential duplicate() {
+        return new LockscreenCredential(mType, mQuality,
+                mCredential != null ? Arrays.copyOf(mCredential, mCredential.length) : null);
+    }
+
+    /**
+     * Zeroize the credential bytes.
+     */
+    public void zeroize() {
+        if (mCredential != null) {
+            Arrays.fill(mCredential, (byte) 0);
+            mCredential = null;
+        }
+    }
+
+    /**
+     * Check if the credential meets minimal length requirement.
+     *
+     * @throws IllegalArgumentException if the credential is too short.
+     */
+    public void checkLength() {
+        if (isNone()) {
+            return;
+        }
+        if (isPattern()) {
+            if (size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
+                throw new IllegalArgumentException("pattern must not be null and at least "
+                        + LockPatternUtils.MIN_LOCK_PATTERN_SIZE + " dots long.");
+            }
+            return;
+        }
+        if (isPassword()) {
+            if (size() < LockPatternUtils.MIN_LOCK_PASSWORD_SIZE) {
+                throw new IllegalArgumentException("password must not be null and at least "
+                        + "of length " + LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
+            }
+            return;
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeInt(mQuality);
+        dest.writeByteArray(mCredential);
+    }
+
+    public static final Parcelable.Creator<LockscreenCredential> CREATOR =
+            new Parcelable.Creator<LockscreenCredential>() {
+
+        @Override
+        public LockscreenCredential createFromParcel(Parcel source) {
+            return new LockscreenCredential(source.readInt(), source.readInt(),
+                    source.createByteArray());
+        }
+
+        @Override
+        public LockscreenCredential[] newArray(int size) {
+            return new LockscreenCredential[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void close() {
+        zeroize();
+    }
+
+    @Override
+    public int hashCode() {
+        // Effective Java — Item 9
+        return ((17 + mType) * 31 + mQuality) * 31 + mCredential.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) return true;
+        if (!(o instanceof LockscreenCredential)) return false;
+        final LockscreenCredential other = (LockscreenCredential) o;
+        return mType == other.mType && mQuality == other.mQuality
+                && Arrays.equals(mCredential, other.mCredential);
+    }
+
+    /**
+     * Converts a CharSequence to a byte array without requiring a toString(), which creates an
+     * additional copy.
+     *
+     * @param chars The CharSequence to convert
+     * @return A byte array representing the input
+     */
+    private static byte[] charSequenceToByteArray(CharSequence chars) {
+        if (chars == null) {
+            return new byte[0];
+        }
+        byte[] bytes = new byte[chars.length()];
+        for (int i = 0; i < chars.length(); i++) {
+            bytes[i] = (byte) chars.charAt(i);
+        }
+        return bytes;
+    }
+}
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/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 697825d..ea0389f 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -168,6 +168,10 @@
     // These are the permitted backup transport service components
     final ArraySet<ComponentName> mBackupTransportWhitelist = new ArraySet<>();
 
+    // These are packages mapped to maps of component class name to default enabled state.
+    final ArrayMap<String, ArrayMap<String, Boolean>> mPackageComponentEnabledState =
+            new ArrayMap<>();
+
     // Package names that are exempted from private API blacklisting
     final ArraySet<String> mHiddenApiPackageWhitelist = new ArraySet<>();
 
@@ -301,6 +305,10 @@
         return mBackupTransportWhitelist;
     }
 
+    public ArrayMap<String, Boolean> getComponentsEnabledStates(String packageName) {
+        return mPackageComponentEnabledState.get(packageName);
+    }
+
     public ArraySet<String> getDisabledUntilUsedPreinstalledCarrierApps() {
         return mDisabledUntilUsedPreinstalledCarrierApps;
     }
@@ -846,6 +854,14 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "component-override": {
+                        if (allowAppConfigs) {
+                            readComponentOverrides(parser, permFile);
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
                     case "backup-transport-whitelisted-service": {
                         if (allowFeatures) {
                             String serviceName = parser.getAttributeValue(null, "service");
@@ -1269,6 +1285,54 @@
         }
     }
 
+    private void readComponentOverrides(XmlPullParser parser, File permFile)
+            throws IOException, XmlPullParserException {
+        String pkgname = parser.getAttributeValue(null, "package");
+        if (pkgname == null) {
+            Slog.w(TAG, "<component-override> without package in "
+                    + permFile + " at " + parser.getPositionDescription());
+            return;
+        }
+
+        pkgname = pkgname.intern();
+
+        final int depth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, depth)) {
+            String name = parser.getName();
+            if ("component".equals(name)) {
+                String clsname = parser.getAttributeValue(null, "class");
+                String enabled = parser.getAttributeValue(null, "enabled");
+                if (clsname == null) {
+                    Slog.w(TAG, "<component> without class in "
+                            + permFile + " at " + parser.getPositionDescription());
+                    return;
+                } else if (enabled == null) {
+                    Slog.w(TAG, "<component> without enabled in "
+                            + permFile + " at " + parser.getPositionDescription());
+                    return;
+                }
+
+                if (clsname.startsWith(".")) {
+                    clsname = pkgname + clsname;
+                }
+
+                clsname = clsname.intern();
+
+                ArrayMap<String, Boolean> componentEnabledStates =
+                        mPackageComponentEnabledState.get(pkgname);
+                if (componentEnabledStates == null) {
+                    componentEnabledStates = new ArrayMap<>();
+                    mPackageComponentEnabledState.put(pkgname,
+                            componentEnabledStates);
+                }
+
+                componentEnabledStates.put(clsname, !"false".equals(enabled));
+            } else {
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
     private static boolean isSystemProcess() {
         return Process.myUid() == Process.SYSTEM_UID;
     }
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index bd4862d..6370253 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -16,6 +16,7 @@
 
 #define ATRACE_TAG ATRACE_TAG_RESOURCES
 
+#include "android-base/logging.h"
 #include "android-base/macros.h"
 #include "android-base/stringprintf.h"
 #include "android-base/unique_fd.h"
@@ -32,7 +33,7 @@
 namespace android {
 
 static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system,
-                        jboolean force_shared_lib, jboolean overlay) {
+                        jboolean force_shared_lib, jboolean overlay, jboolean for_loader) {
   ScopedUtfChars path(env, java_path);
   if (path.c_str() == nullptr) {
     return 0;
@@ -46,7 +47,7 @@
   } else if (force_shared_lib) {
     apk_assets = ApkAssets::LoadAsSharedLibrary(path.c_str(), system);
   } else {
-    apk_assets = ApkAssets::Load(path.c_str(), system);
+    apk_assets = ApkAssets::Load(path.c_str(), system, for_loader);
   }
 
   if (apk_assets == nullptr) {
@@ -58,7 +59,8 @@
 }
 
 static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
-                              jstring friendly_name, jboolean system, jboolean force_shared_lib) {
+                              jstring friendly_name, jboolean system, jboolean force_shared_lib,
+                              jboolean for_loader) {
   ScopedUtfChars friendly_name_utf8(env, friendly_name);
   if (friendly_name_utf8.c_str() == nullptr) {
     return 0;
@@ -80,7 +82,9 @@
 
   std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd),
                                                                       friendly_name_utf8.c_str(),
-                                                                      system, force_shared_lib);
+                                                                      system, force_shared_lib,
+                                                                      for_loader);
+
   if (apk_assets == nullptr) {
     std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d",
                                                friendly_name_utf8.c_str(), dup_fd.get());
@@ -90,6 +94,60 @@
   return reinterpret_cast<jlong>(apk_assets.release());
 }
 
+static jlong NativeLoadArsc(JNIEnv* env, jclass /*clazz*/, jstring java_path,
+                            jboolean for_loader) {
+  ScopedUtfChars path(env, java_path);
+  if (path.c_str() == nullptr) {
+    return 0;
+  }
+
+  ATRACE_NAME(base::StringPrintf("LoadApkAssetsArsc(%s)", path.c_str()).c_str());
+
+  std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadArsc(path.c_str(), for_loader);
+
+  if (apk_assets == nullptr) {
+    std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str());
+    jniThrowException(env, "java/io/IOException", error_msg.c_str());
+    return 0;
+  }
+  return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static jlong NativeLoadArscFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
+                                  jstring friendly_name, jboolean for_loader) {
+  ScopedUtfChars friendly_name_utf8(env, friendly_name);
+  if (friendly_name_utf8.c_str() == nullptr) {
+    return 0;
+  }
+
+  int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
+  ATRACE_NAME(base::StringPrintf("LoadApkAssetsArscFd(%d)", fd).c_str());
+  if (fd < 0) {
+    jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+    return 0;
+  }
+
+  unique_fd dup_fd(::fcntl(fd, F_DUPFD_CLOEXEC, 0));
+  if (dup_fd < 0) {
+    jniThrowIOException(env, errno);
+    return 0;
+  }
+
+  std::unique_ptr<const ApkAssets> apk_assets =
+      ApkAssets::LoadArsc(std::move(dup_fd), friendly_name_utf8.c_str(), for_loader);
+  if (apk_assets == nullptr) {
+    std::string error_msg = base::StringPrintf("Failed to load asset path from fd %d", fd);
+    jniThrowException(env, "java/io/IOException", error_msg.c_str());
+    return 0;
+  }
+  return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jboolean for_loader) {
+  std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadEmpty(for_loader);
+  return reinterpret_cast<jlong>(apk_assets.release());
+}
+
 static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
   delete reinterpret_cast<ApkAssets*>(ptr);
 }
@@ -138,9 +196,13 @@
 
 // JNI registration.
 static const JNINativeMethod gApkAssetsMethods[] = {
-    {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad},
-    {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J",
+    {"nativeLoad", "(Ljava/lang/String;ZZZZ)J", (void*)NativeLoad},
+    {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZZ)J",
         (void*)NativeLoadFromFd},
+    {"nativeLoadArsc", "(Ljava/lang/String;Z)J", (void*)NativeLoadArsc},
+    {"nativeLoadArscFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;Z)J",
+        (void*)NativeLoadArscFromFd},
+    {"nativeLoadEmpty", "(Z)J", (void*)NativeLoadEmpty},
     {"nativeDestroy", "(J)V", (void*)NativeDestroy},
     {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
     {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 9445319..d62d2d9 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -139,8 +139,8 @@
         "nativePrivateClean", "nativeSharedClean", "nativeSwappedOut", "nativeSwappedOutPss" }
 };
 
-jfieldID otherStats_field;
-jfieldID hasSwappedOutPss_field;
+static jfieldID otherStats_field;
+static jfieldID hasSwappedOutPss_field;
 
 struct stats_t {
     int pss;
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index daf33f6..c7b36d0 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -108,7 +108,7 @@
   jmethodID put;
 } gArrayMapOffsets;
 
-jclass g_stringClass = nullptr;
+static jclass g_stringClass = nullptr;
 
 // ----------------------------------------------------------------------------
 
@@ -763,6 +763,41 @@
   return reinterpret_cast<jlong>(xml_tree.release());
 }
 
+static jlong NativeOpenXmlAssetFd(JNIEnv* env, jobject /*clazz*/, jlong ptr, int jcookie,
+                                  jobject file_descriptor) {
+  int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
+  ATRACE_NAME(base::StringPrintf("AssetManager::OpenXmlAssetFd(%d)", fd).c_str());
+  if (fd < 0) {
+    jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+    return 0;
+  }
+
+  base::unique_fd dup_fd(::fcntl(fd, F_DUPFD_CLOEXEC, 0));
+  if (dup_fd < 0) {
+    jniThrowIOException(env, errno);
+    return 0;
+  }
+
+  std::unique_ptr<Asset>
+      asset(Asset::createFromFd(dup_fd.release(), nullptr, Asset::AccessMode::ACCESS_BUFFER));
+
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
+
+  // May be nullptr.
+  const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie);
+
+  std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table);
+  status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true);
+  asset.reset();
+
+  if (err != NO_ERROR) {
+    jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
+    return 0;
+  }
+  return reinterpret_cast<jlong>(xml_tree.release());
+}
+
 static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
                                    jshort density, jobject typed_value,
                                    jboolean resolve_references) {
@@ -1564,6 +1599,7 @@
     {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
      (void*)NativeOpenNonAssetFd},
     {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
+    {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
 
     // AssetManager resource methods.
     {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue},
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index e406e22..2232393 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -992,6 +992,31 @@
     return IPCThreadState::self()->blockUntilThreadAvailable();
 }
 
+static jobject android_os_Binder_waitForService(
+        JNIEnv *env,
+        jclass /* clazzObj */,
+        jstring serviceNameObj) {
+
+    const jchar* serviceName = env->GetStringCritical(serviceNameObj, nullptr);
+    if (!serviceName) {
+        signalExceptionForError(env, nullptr, BAD_VALUE, true /*canThrowRemoteException*/);
+        return nullptr;
+    }
+    String16 nameCopy = String16(reinterpret_cast<const char16_t *>(serviceName),
+            env->GetStringLength(serviceNameObj));
+    env->ReleaseStringCritical(serviceNameObj, serviceName);
+
+    auto sm = android::defaultServiceManager();
+    sp<IBinder> service = sm->waitForService(nameCopy);
+
+    if (!service) {
+        signalExceptionForError(env, nullptr, NAME_NOT_FOUND, true /*canThrowRemoteException*/);
+        return nullptr;
+    }
+
+    return javaObjectForIBinder(env, service);
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gBinderMethods[] = {
@@ -1019,7 +1044,8 @@
     { "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
     { "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
     { "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
-    { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }
+    { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable },
+    { "waitForService", "(Ljava/lang/String;)Landroid/os/IBinder;", (void*)android_os_Binder_waitForService }
 };
 
 const char* const kBinderPathName = "android/os/Binder";
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/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index af34e7b7..bf1cea8 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -112,7 +112,9 @@
 }
 
 static jobject android_view_InputChannel_createInputChannel(JNIEnv* env,
-        std::unique_ptr<NativeInputChannel> nativeInputChannel) {
+        sp<InputChannel> inputChannel) {
+    std::unique_ptr<NativeInputChannel> nativeInputChannel =
+            std::make_unique<NativeInputChannel>(inputChannel);
     jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz,
             gInputChannelClassInfo.ctor);
     if (inputChannelObj) {
@@ -143,14 +145,12 @@
         return nullptr;
     }
 
-    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
-            std::make_unique<NativeInputChannel>(serverChannel));
+    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env, serverChannel);
     if (env->ExceptionCheck()) {
         return nullptr;
     }
 
-    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
-            std::make_unique<NativeInputChannel>(clientChannel));
+    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, clientChannel);
     if (env->ExceptionCheck()) {
         return nullptr;
     }
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index b8c5270..94be61f 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2412,4 +2412,18 @@
     // OS: R
     SETTINGS_WIFI_CONFIGURE_NETWORK = 1800;
 
+    // OPEN: Settings > Accessibility > Magnification
+    // CATEGORY: SETTINGS
+    // OS: R
+    // Note: Shows up only when Magnify with shortcut is enabled
+    // and under accessibility button mode.
+    DIALOG_TOGGLE_SCREEN_MAGNIFICATION_ACCESSIBILITY_BUTTON = 1801;
+
+    // OPEN: Settings > Accessibility > Magnification
+    // CATEGORY: SETTINGS
+    // OS: R
+    // Note: Shows up only when Magnify with shortcut is enabled.
+    // and under gesture navigation mode.
+    DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION = 1802;
+
 }
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index 301fa13..d010c8f 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -114,6 +114,13 @@
         optional int32 distraction_flags = 10;
     }
 
+    message InstallSourceProto {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        // The package that requested the installation of this one.
+        optional string initiating_package_name = 1;
+    }
+
     // Name of package. e.g. "com.android.providers.telephony".
     optional string name = 1;
     // UID for this package as assigned by Android OS.
@@ -133,4 +140,6 @@
     repeated SplitProto splits = 8;
     // Per-user package info.
     repeated UserInfoProto users = 9;
+    // Where the request to install this package came from,
+    optional InstallSourceProto install_source = 10;
 }
diff --git a/core/proto/android/service/procstats.proto b/core/proto/android/service/procstats.proto
index f49a044..ad7299d 100644
--- a/core/proto/android/service/procstats.proto
+++ b/core/proto/android/service/procstats.proto
@@ -241,12 +241,25 @@
     repeated StateStats active_state_stats = 6;
 }
 
-// Next Tag: 3
+// Next Tag: 7
 message PackageAssociationProcessStatsProto {
     option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
     // Name of the target component.
     optional string component_name = 1;
+
+    // Total count of the times this association appeared.
+    optional int32 total_count = 3;
+
+    // Millisecond uptime total duration this association was around.
+    optional int64 total_duration_ms = 4;
+
+    // Total count of the times this association became actively impacting its target process.
+    optional int32 active_count = 5;
+
+    // Millisecond uptime total duration this association was around.
+    optional int64 active_duration_ms = 6;
+
     // Information on one source in this association.
     repeated PackageAssociationSourceProcessStatsProto sources = 2;
 }
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 0821d14..15813a1 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -113,9 +113,9 @@
   PROVISIONING_PREPROVISIONING_ACTIVITY_TIME_MS = 87;
   PROVISIONING_ENCRYPT_DEVICE_ACTIVITY_TIME_MS = 88;
   PROVISIONING_WEB_ACTIVITY_TIME_MS = 89;
-  PROVISIONING_TRAMPOLINE_ACTIVITY_TIME_MS = 90;
-  PROVISIONING_POST_ENCRYPTION_ACTIVITY_TIME_MS = 91;
-  PROVISIONING_FINALIZATION_ACTIVITY_TIME_MS = 92;
+  PROVISIONING_TRAMPOLINE_ACTIVITY_TIME_MS = 90 [deprecated=true];
+  PROVISIONING_POST_ENCRYPTION_ACTIVITY_TIME_MS = 91 [deprecated=true];
+  PROVISIONING_FINALIZATION_ACTIVITY_TIME_MS = 92 [deprecated=true];
   PROVISIONING_NETWORK_TYPE = 93;
   PROVISIONING_ACTION = 94;
   PROVISIONING_EXTRAS = 95;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b030b33..f98ea2c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1624,7 +1624,7 @@
          @hide This should only be used by Settings and SystemUI.
     -->
     <permission android:name="android.permission.NETWORK_SETTINGS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- Allows SetupWizard to call methods in Networking services
          <p>Not for use by any other third-party or privileged applications.
@@ -2138,12 +2138,12 @@
 
     <!-- Must be required by a telephony data service to ensure that only the
          system can bind to it.
-         <p>Protection level: signature
+         <p>Protection level: signature|telephony
          @SystemApi
          @hide
     -->
     <permission android:name="android.permission.BIND_TELEPHONY_DATA_SERVICE"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- Must be required by a NetworkService to ensure that only the
          system can bind to it.
@@ -2164,11 +2164,11 @@
 
     <!-- @SystemApi Must be required by an EuiccService to ensure that only the system can bind to
          it.
-         <p>Protection level: signature
+         <p>Protection level: signature|telephony
          @hide
     -->
     <permission android:name="android.permission.BIND_EUICC_SERVICE"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|telephony" />
 
     <!-- ================================== -->
     <!-- Permissions for sdcard interaction -->
@@ -2955,7 +2955,7 @@
          @hide
     -->
     <permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony|wifi" />
 
     <!-- @SystemApi Allows an application to use
          {@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
@@ -3511,6 +3511,13 @@
     <permission android:name="android.permission.GET_RUNTIME_PERMISSIONS"
                 android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows the system to restore runtime permission state. This might grant
+    permissions, hence this is a more scoped, less powerful variant of GRANT_RUNTIME_PERMISSIONS.
+    Among other restrictions this cannot override user choices.
+    @hide -->
+    <permission android:name="android.permission.RESTORE_RUNTIME_PERMISSIONS"
+                android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to change policy_fixed permissions.
     @hide -->
     <permission android:name="android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"
@@ -3740,7 +3747,7 @@
         @hide
     -->
    <permission android:name="android.permission.DEVICE_POWER"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- Allows toggling battery saver on the system.
          Superseded by DEVICE_POWER permission. @hide @SystemApi
@@ -3775,13 +3782,13 @@
          <p>Not for use by third-party applications.
     -->
     <permission android:name="android.permission.BROADCAST_SMS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- Allows an application to broadcast a WAP PUSH receipt notification.
          <p>Not for use by third-party applications.
     -->
     <permission android:name="android.permission.BROADCAST_WAP_PUSH"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- @SystemApi Allows an application to broadcast privileged networking requests.
          <p>Not for use by third-party applications.
@@ -4396,13 +4403,13 @@
          {@link android.provider.BlockedNumberContract}.
          @hide -->
     <permission android:name="android.permission.READ_BLOCKED_NUMBERS"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|telephony" />
 
     <!-- Allows the holder to write blocked numbers. See
          {@link android.provider.BlockedNumberContract}.
          @hide -->
     <permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|telephony" />
 
     <!-- Must be required by an {@link android.service.vr.VrListenerService}, to ensure that only
          the system can bind to it.
diff --git a/core/res/res/layout/resolve_list_item.xml b/core/res/res/layout/resolve_list_item.xml
index 0bdb25a..4857095 100644
--- a/core/res/res/layout/resolve_list_item.xml
+++ b/core/res/res/layout/resolve_list_item.xml
@@ -22,8 +22,6 @@
               android:layout_height="wrap_content"
               android:layout_width="match_parent"
               android:minHeight="?attr/listPreferredItemHeightSmall"
-              android:paddingTop="4dp"
-              android:paddingBottom="4dp"
               android:background="?attr/activatedBackgroundIndicator">
 
     <!-- Activity icon when presenting dialog
@@ -32,7 +30,8 @@
                android:layout_width="@dimen/resolver_icon_size"
                android:layout_height="@dimen/resolver_icon_size"
                android:layout_gravity="start|center_vertical"
-               android:layout_marginStart="?attr/listPreferredItemPaddingStart"
+               android:layout_marginStart="@dimen/resolver_icon_margin"
+               android:layout_marginEnd="@dimen/resolver_icon_margin"
                android:layout_marginTop="12dp"
                android:layout_marginBottom="12dp"
                android:scaleType="fitCenter" />
@@ -40,8 +39,7 @@
     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:gravity="start|center_vertical"
               android:orientation="vertical"
-              android:paddingStart="?attr/listPreferredItemPaddingStart"
-              android:paddingEnd="?attr/listPreferredItemPaddingEnd"
+              android:paddingEnd="@dimen/resolver_edge_margin"
               android:layout_height="wrap_content"
               android:layout_width="wrap_content"
               android:layout_gravity="start|center_vertical">
@@ -49,14 +47,20 @@
         <TextView android:id="@android:id/text1"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
-                  android:textAppearance="?attr/textAppearanceMedium"
-                  android:textColor="?attr/textColorPrimary"
+                  android:layout_gravity="start|center_vertical"
+                  android:textColor="?android:attr/textColorPrimary"
+                  android:fontFamily="@android:string/config_bodyFontFamily"
+                  android:textSize="16sp"
                   android:minLines="1"
                   android:maxLines="1"
                   android:ellipsize="marquee" />
         <!-- Extended activity info to distinguish between duplicate activity names -->
         <TextView android:id="@android:id/text2"
-                  android:textAppearance="?android:attr/textAppearanceSmall"
+                  android:textColor="?android:attr/textColorSecondary"
+                  android:fontFamily="@android:string/config_bodyFontFamily"
+                  android:layout_gravity="start|center_vertical"
+                  android:textSize="14sp"
+                  android:visibility="gone"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:minLines="1"
diff --git a/core/res/res/layout/resolver_different_item_header.xml b/core/res/res/layout/resolver_different_item_header.xml
index 7d9ffd7..0a35edc 100644
--- a/core/res/res/layout/resolver_different_item_header.xml
+++ b/core/res/res/layout/resolver_different_item_header.xml
@@ -22,12 +22,12 @@
     android:layout_height="wrap_content"
     android:layout_alwaysShow="true"
     android:text="@string/use_a_different_app"
-    android:minHeight="56dp"
-    android:textAppearance="?android:attr/textAppearanceMedium"
+    android:textColor="?android:attr/textColorPrimary"
+    android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+    android:textSize="16sp"
     android:gravity="start|center_vertical"
-    android:paddingStart="16dp"
-    android:paddingEnd="16dp"
-    android:paddingTop="8dp"
-    android:paddingBottom="8dp"
-    android:elevation="8dp"
-    />
+    android:paddingStart="@dimen/resolver_edge_margin"
+    android:paddingEnd="@dimen/resolver_edge_margin"
+    android:paddingTop="@dimen/resolver_small_margin"
+    android:paddingBottom="@dimen/resolver_edge_margin"
+    android:elevation="1dp" />
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 1dd4207..6e45e7a 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -29,16 +29,18 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_alwaysShow="true"
-        android:elevation="8dp"
-        android:background="?attr/colorBackgroundFloating">
+        android:elevation="@dimen/resolver_elevation"
+        android:paddingTop="@dimen/resolver_small_margin"
+        android:paddingStart="@dimen/resolver_edge_margin"
+        android:paddingEnd="@dimen/resolver_edge_margin"
+        android:paddingBottom="@dimen/resolver_edge_margin"
+        android:background="@drawable/bottomsheet_background">
 
         <TextView
             android:id="@+id/profile_button"
             android:layout_width="wrap_content"
             android:layout_height="48dp"
             android:layout_marginEnd="8dp"
-            android:paddingStart="8dp"
-            android:paddingEnd="8dp"
             android:visibility="gone"
             style="?attr/borderlessButtonStyle"
             android:textAppearance="?attr/textAppearanceButton"
@@ -50,36 +52,49 @@
 
         <TextView
             android:id="@+id/title"
-            android:layout_width="wrap_content"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:minHeight="56dp"
-            android:textAppearance="?attr/textAppearanceMedium"
-            android:gravity="start|center_vertical"
-            android:paddingStart="?attr/dialogPreferredPadding"
-            android:paddingEnd="?attr/dialogPreferredPadding"
-            android:paddingTop="8dp"
             android:layout_below="@id/profile_button"
             android:layout_alignParentStart="true"
-            android:paddingBottom="8dp" />
+            android:textColor="?android:attr/textColorPrimary"
+            android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+            android:textSize="16sp"
+            android:gravity="start|center_vertical" />
     </RelativeLayout>
 
+    <View
+        android:layout_alwaysShow="true"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="?attr/colorBackgroundFloating"
+        android:foreground="?attr/dividerVertical" />
     <ListView
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:id="@+id/resolver_list"
         android:clipToPadding="false"
-        android:scrollbarStyle="outsideOverlay"
         android:background="?attr/colorBackgroundFloating"
-        android:elevation="8dp"
+        android:elevation="@dimen/resolver_elevation"
         android:nestedScrollingEnabled="true"
+        android:scrollbarStyle="outsideOverlay"
         android:scrollIndicators="top|bottom"
-        android:divider="@null" />
+        android:divider="?attr/dividerVertical"
+        android:footerDividersEnabled="false"
+        android:headerDividersEnabled="false"
+        android:dividerHeight="1dp" />
+    <View
+        android:layout_alwaysShow="true"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="?attr/colorBackgroundFloating"
+        android:foreground="?attr/dividerVertical" />
+
 
     <TextView android:id="@+id/empty"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:background="?attr/colorBackgroundFloating"
-              android:elevation="8dp"
+              android:elevation="@dimen/resolver_elevation"
               android:layout_alwaysShow="true"
               android:text="@string/noApplications"
               android:padding="32dp"
@@ -102,18 +117,19 @@
         android:background="?attr/colorBackgroundFloating"
         android:paddingTop="@dimen/resolver_button_bar_spacing"
         android:paddingBottom="@dimen/resolver_button_bar_spacing"
-        android:paddingStart="12dp"
-        android:paddingEnd="12dp"
-        android:elevation="8dp">
+        android:paddingStart="@dimen/resolver_edge_margin"
+        android:paddingEnd="@dimen/resolver_small_margin"
+        android:elevation="@dimen/resolver_elevation">
 
         <Button
             android:id="@+id/button_once"
             android:layout_width="wrap_content"
             android:layout_gravity="start"
             android:maxLines="2"
-            style="?attr/buttonBarNegativeButtonStyle"
-            android:minHeight="@dimen/alert_dialog_button_bar_height"
+            style="?attr/buttonBarButtonStyle"
+            android:fontFamily="@android:string/config_headlineFontFamilyMedium"
             android:layout_height="wrap_content"
+            android:textAllCaps="false"
             android:enabled="false"
             android:text="@string/activity_resolver_use_once"
             android:onClick="onButtonClick" />
@@ -123,8 +139,9 @@
             android:layout_width="wrap_content"
             android:layout_gravity="end"
             android:maxLines="2"
-            android:minHeight="@dimen/alert_dialog_button_bar_height"
-            style="?attr/buttonBarPositiveButtonStyle"
+            style="?attr/buttonBarButtonStyle"
+            android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+            android:textAllCaps="false"
             android:layout_height="wrap_content"
             android:enabled="false"
             android:text="@string/activity_resolver_use_always"
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index 740a7eb..dbba0b7 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -29,22 +29,22 @@
         android:layout_height="wrap_content"
         android:layout_alwaysShow="true"
         android:orientation="vertical"
-        android:background="?attr/colorBackgroundFloating"
-        android:elevation="8dp">
+        android:background="@drawable/bottomsheet_background"
+        android:paddingTop="@dimen/resolver_small_margin"
+        android:elevation="@dimen/resolver_elevation">
 
         <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="64dp"
-            android:orientation="horizontal">
-
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingBottom="@dimen/resolver_edge_margin"
+            android:paddingEnd="@dimen/resolver_edge_margin">
             <ImageView
                 android:id="@+id/icon"
-                android:layout_width="24dp"
-                android:layout_height="24dp"
+                android:layout_width="@dimen/resolver_icon_size"
+                android:layout_height="@dimen/resolver_icon_size"
                 android:layout_gravity="start|top"
-                android:layout_marginStart="16dp"
-                android:layout_marginEnd="16dp"
-                android:layout_marginTop="20dp"
+                android:layout_marginStart="@dimen/resolver_icon_margin"
                 android:src="@drawable/resolver_icon_placeholder"
                 android:scaleType="fitCenter" />
 
@@ -52,9 +52,11 @@
                 android:id="@+id/title"
                 android:layout_width="0dp"
                 android:layout_weight="1"
-                android:layout_height="?attr/listPreferredItemHeight"
-                android:layout_marginStart="16dp"
-                android:textAppearance="?attr/textAppearanceMedium"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/resolver_icon_margin"
+                android:textColor="?android:attr/textColorPrimary"
+                android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+                android:textSize="16sp"
                 android:gravity="start|center_vertical"
                 android:paddingEnd="16dp" />
 
@@ -107,21 +109,22 @@
             android:orientation="horizontal"
             android:layoutDirection="locale"
             android:measureWithLargestChild="true"
-            android:paddingTop="8dp"
-            android:paddingBottom="8dp"
-            android:paddingStart="12dp"
-            android:paddingEnd="12dp"
-            android:elevation="8dp">
+            android:paddingTop="@dimen/resolver_button_bar_spacing"
+            android:paddingBottom="@dimen/resolver_button_bar_spacing"
+            android:paddingStart="@dimen/resolver_edge_margin"
+            android:paddingEnd="@dimen/resolver_small_margin"
+            android:elevation="@dimen/resolver_elevation">
 
             <Button
                 android:id="@+id/button_once"
                 android:layout_width="wrap_content"
                 android:layout_gravity="start"
                 android:maxLines="2"
-                style="?attr/buttonBarNegativeButtonStyle"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
+                style="?attr/buttonBarButtonStyle"
+                android:fontFamily="@android:string/config_headlineFontFamilyMedium"
                 android:layout_height="wrap_content"
                 android:enabled="false"
+                android:textAllCaps="false"
                 android:text="@string/activity_resolver_use_once"
                 android:onClick="onButtonClick" />
 
@@ -130,29 +133,40 @@
                 android:layout_width="wrap_content"
                 android:layout_gravity="end"
                 android:maxLines="2"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                style="?attr/buttonBarPositiveButtonStyle"
+                style="?attr/buttonBarButtonStyle"
+                android:fontFamily="@android:string/config_headlineFontFamilyMedium"
                 android:layout_height="wrap_content"
                 android:enabled="false"
+                android:textAllCaps="false"
                 android:text="@string/activity_resolver_use_always"
                 android:onClick="onButtonClick" />
         </LinearLayout>
-
-        <View
-            android:layout_width="match_parent"
-            android:layout_height="1dp"
-            android:background="?attr/dividerVertical" />
     </LinearLayout>
 
+    <View
+        android:layout_alwaysShow="true"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="?attr/colorBackgroundFloating"
+        android:foreground="?attr/dividerVertical" />
     <ListView
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:id="@+id/resolver_list"
         android:clipToPadding="false"
-        android:scrollbarStyle="outsideOverlay"
         android:background="?attr/colorBackgroundFloating"
-        android:elevation="8dp"
+        android:elevation="@dimen/resolver_elevation"
         android:nestedScrollingEnabled="true"
-        android:divider="@null" />
-
+        android:scrollbarStyle="outsideOverlay"
+        android:scrollIndicators="top|bottom"
+        android:divider="?attr/dividerVertical"
+        android:footerDividersEnabled="false"
+        android:headerDividersEnabled="false"
+        android:dividerHeight="1dp" />
+    <View
+        android:layout_alwaysShow="true"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="?attr/colorBackgroundFloating"
+        android:foreground="?attr/dividerVertical" />
 </com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index a301702..ffcfe43 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -295,6 +295,12 @@
         <!-- Additional flag from base permission type: this permission can be automatically
             granted to the system app predictor -->
         <flag name="appPredictor" value="0x200000" />
+        <!-- Additional flag from base permission type: this permission can be automatically
+            granted to the system telephony apps -->
+        <flag name="telephony" value="0x400000" />
+        <!-- Additional flag from base permission type: this permission can be automatically
+            granted to the system wifi app-->
+        <flag name="wifi" value="0x800000" />
     </attr>
 
     <!-- Flags indicating more context for a permission group. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3fef7a2d..9bff88a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3687,6 +3687,21 @@
      -->
     <string name="config_defaultWellbeingPackage" translatable="false"></string>
 
+    <!-- The package name for the system telephony apps.
+         This package must be trusted, as it will be granted with permissions with special telephony
+         protection level. Note, framework by default support multiple telephony apps, each package
+         name is separated by comma.
+         Example: "com.android.phone,com.android.stk,com.android.providers.telephony"
+     -->
+    <string name="config_telephonyPackages" translatable="false">"com.android.phone,com.android.stk,com.android.providers.telephony,com.android.ons"</string>
+
+    <!-- The package name for the default system wifi app.
+         This package must be trusted, as it has the permissions to control wifi
+         connectivity on the device.
+         Example: "com.android.wifi"
+     -->
+    <string name="config_wifiPackage" translatable="false">"com.android.wifi"</string>
+
     <!-- The component name for the default system attention service.
          This service must be trusted, as it can be activated without explicit consent of the user.
          See android.attention.AttentionManagerService.
@@ -4304,11 +4319,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/dimens.xml b/core/res/res/values/dimens.xml
index 609659b..a01bbe3 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -750,7 +750,7 @@
 
     <dimen name="seekbar_thumb_exclusion_max_size">48dp</dimen>
 
-    <!-- chooser (sharesheet) spacing -->
+    <!-- chooser/resolver (sharesheet) spacing -->
     <dimen name="chooser_corner_radius">8dp</dimen>
     <dimen name="chooser_row_text_option_translate">25dp</dimen>
     <dimen name="chooser_view_spacing">18dp</dimen>
@@ -759,11 +759,15 @@
     <dimen name="chooser_preview_image_font_size">20sp</dimen>
     <dimen name="chooser_preview_image_border">1dp</dimen>
     <dimen name="chooser_preview_width">-1px</dimen>
-    <dimen name="resolver_icon_size">42dp</dimen>
-    <dimen name="resolver_button_bar_spacing">8dp</dimen>
-    <dimen name="resolver_badge_size">18dp</dimen>
     <dimen name="chooser_target_width">90dp</dimen>
     <dimen name="chooser_header_scroll_elevation">4dp</dimen>
     <dimen name="chooser_max_collapsed_height">288dp</dimen>
     <dimen name="chooser_direct_share_label_placeholder_max_width">72dp</dimen>
+    <dimen name="resolver_icon_size">32dp</dimen>
+    <dimen name="resolver_button_bar_spacing">8dp</dimen>
+    <dimen name="resolver_badge_size">18dp</dimen>
+    <dimen name="resolver_icon_margin">16dp</dimen>
+    <dimen name="resolver_small_margin">18dp</dimen>
+    <dimen name="resolver_edge_margin">24dp</dimen>
+    <dimen name="resolver_elevation">1dp</dimen>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 363bc9d..42cd2cd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3467,6 +3467,8 @@
   <java-symbol type="string" name="config_defaultAutofillService" />
   <java-symbol type="string" name="config_defaultTextClassifierPackage" />
   <java-symbol type="string" name="config_defaultWellbeingPackage" />
+  <java-symbol type="string" name="config_telephonyPackages" />
+  <java-symbol type="string" name="config_wifiPackage" />
   <java-symbol type="string" name="config_defaultContentCaptureService" />
   <java-symbol type="string" name="config_defaultAugmentedAutofillService" />
   <java-symbol type="string" name="config_defaultAppPredictionService" />
@@ -3819,6 +3821,10 @@
   <java-symbol type="dimen" name="resolver_icon_size"/>
   <java-symbol type="dimen" name="resolver_badge_size"/>
   <java-symbol type="dimen" name="resolver_button_bar_spacing"/>
+  <java-symbol type="dimen" name="resolver_icon_margin"/>
+  <java-symbol type="dimen" name="resolver_small_margin"/>
+  <java-symbol type="dimen" name="resolver_edge_margin"/>
+  <java-symbol type="dimen" name="resolver_elevation"/>
 
   <!-- For DropBox -->
   <java-symbol type="integer" name="config_dropboxLowPriorityBroadcastRateLimitPeriod" />
diff --git a/core/tests/ResourceLoaderTests/Android.bp b/core/tests/ResourceLoaderTests/Android.bp
new file mode 100644
index 0000000..53db832
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/Android.bp
@@ -0,0 +1,63 @@
+//
+// 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.
+//
+
+android_test {
+    name: "FrameworksResourceLoaderTests",
+    srcs: [
+        "src/**/*.kt"
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: [
+        "androidx.test.espresso.core",
+        "androidx.test.ext.junit",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+        "truth-prebuilt",
+    ],
+    resource_zips: [ ":FrameworksResourceLoaderTestsAssets" ],
+    test_suites: ["device-tests"],
+    sdk_version: "test_current",
+    aaptflags: [
+        "--no-compress",
+    ],
+    data: [
+        ":FrameworksResourceLoaderTestsOverlay",
+        ":FrameworksResourceLoaderTestsSplitOne",
+        ":FrameworksResourceLoaderTestsSplitTwo",
+    ],
+    java_resources: [ "NonAsset.txt" ]
+}
+
+filegroup {
+    name: "FrameworksResourceLoaderTestsResources",
+    srcs: ["resources"],
+}
+
+genrule {
+    name: "FrameworksResourceLoaderTestsAssets",
+    srcs: [
+        ":framework-res",
+        ":FrameworksResourceLoaderTestsResources",
+    ],
+    tools: [ ":aapt2", ":soong_zip" ],
+    tool_files: [ "resources/compileAndLink.sh" ],
+    cmd: "$(location resources/compileAndLink.sh) $(location :aapt2) $(location :soong_zip) $(genDir) $(in) $(in)",
+    out: [ "out.zip" ]
+}
diff --git a/core/tests/ResourceLoaderTests/AndroidManifest.xml b/core/tests/ResourceLoaderTests/AndroidManifest.xml
new file mode 100644
index 0000000..00b4ccb
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<!-- Split loading is tested separately, so this must be marked isolated -->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.res.loader.test"
+    android:isolatedSplits="true"
+    >
+
+    <uses-sdk android:minSdkVersion="29"/>
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+
+        <activity
+            android:name=".TestActivity"
+            android:configChanges="orientation"
+            />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="ResourceLoaderTests"
+        android:targetPackage="android.content.res.loader.test"
+        />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/AndroidTest.xml b/core/tests/ResourceLoaderTests/AndroidTest.xml
new file mode 100644
index 0000000..702151d
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/AndroidTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<configuration description="Test module config for ResourceLoaderTests">
+    <option name="test-tag" value="ResourceLoaderTests" />
+
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="cleanup-apks" value="true" />
+        <!-- The following value cannot be multi-line as whitespace is parsed by the installer -->
+        <option name="split-apk-file-names"
+            value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTestsSplitOne.apk,FrameworksResourceLoaderTestsSplitTwo.apk" />
+        <option name="test-file-name" value="FrameworksResourceLoaderTestsOverlay.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command"
+            value="cmd overlay disable android.content.res.loader.test.overlay" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.content.res.loader.test" />
+    </test>
+</configuration>
diff --git a/core/tests/ResourceLoaderTests/NonAsset.txt b/core/tests/ResourceLoaderTests/NonAsset.txt
new file mode 100644
index 0000000..5c0b2cc
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/NonAsset.txt
@@ -0,0 +1 @@
+Outside assets directory
diff --git a/core/tests/ResourceLoaderTests/SplitOne/Android.bp b/core/tests/ResourceLoaderTests/SplitOne/Android.bp
new file mode 100644
index 0000000..897897f
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/SplitOne/Android.bp
@@ -0,0 +1,19 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "FrameworksResourceLoaderTestsSplitOne"
+}
diff --git a/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml b/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml
new file mode 100644
index 0000000..b14bd86
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.res.loader.test"
+    split="split_one"
+    >
+
+    <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+    <application android:hasCode="false" />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml b/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml
new file mode 100644
index 0000000..3c215eb
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <public type="string" name="split_overlaid" id="0x7f040001" />
+    <string name="split_overlaid">Split ONE Overlaid</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/assets/Asset.txt b/core/tests/ResourceLoaderTests/assets/Asset.txt
new file mode 100644
index 0000000..03f9a0f
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/assets/Asset.txt
@@ -0,0 +1 @@
+In assets directory
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar
new file mode 100644
index 0000000..a12e33a
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar
new file mode 100644
index 0000000..182cbab
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar
new file mode 100644
index 0000000..e6b5f15b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar
new file mode 100644
index 0000000..e9c743c
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar
new file mode 100644
index 0000000..cd05360
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar
new file mode 100644
index 0000000..dc8aa90
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar
new file mode 100644
index 0000000..8a672ba
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar
new file mode 100644
index 0000000..56f3d1e
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar
new file mode 100644
index 0000000..663d312
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test.jar
new file mode 100644
index 0000000..5f6e4b8
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-test.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/overlay/Android.bp b/core/tests/ResourceLoaderTests/overlay/Android.bp
new file mode 100644
index 0000000..63e7e61
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/overlay/Android.bp
@@ -0,0 +1,20 @@
+// Copyright (C) 2018 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.
+
+android_test {
+    name: "FrameworksResourceLoaderTestsOverlay",
+    sdk_version: "current",
+
+    aaptflags: ["--no-resource-removal"],
+}
diff --git a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml b/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
new file mode 100644
index 0000000..942f7da
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.res.loader.test.overlay"
+    >
+
+    <application android:hasCode="false" />
+
+    <overlay android:targetPackage="android.content.res.loader.test" />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
new file mode 100644
index 0000000..348bb35
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+
+    <string name="loader_path_change_test">Overlaid</string>
+
+</resources>
diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png
new file mode 100644
index 0000000..efd71ee
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml
new file mode 100644
index 0000000..d1211c5
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<color
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="#B2D2F2"
+    />
diff --git a/core/tests/ResourceLoaderTests/res/layout/layout.xml b/core/tests/ResourceLoaderTests/res/layout/layout.xml
new file mode 100644
index 0000000..d59059b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/layout/layout.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    />
+
diff --git a/core/tests/ResourceLoaderTests/res/values/strings.xml b/core/tests/ResourceLoaderTests/res/values/strings.xml
new file mode 100644
index 0000000..28b8f73
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+
+    <string name="loader_path_change_test">Not overlaid</string>
+    <string name="split_overlaid">Not overlaid</string>
+
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml
new file mode 100644
index 0000000..5dd8a96
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.res.loader.test"
+    >
+
+    <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+    <application/>
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml
new file mode 100644
index 0000000..5a92ae9
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<!-- Mocks the framework package name so that AAPT2 assigns the correct package -->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android"
+    >
+
+    <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+    <application/>
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/compileAndLink.sh b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh
new file mode 100755
index 0000000..885f681
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+# 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.
+
+aapt2=$1
+soong_zip=$2
+genDir=$3
+FRAMEWORK_RES_APK=$4
+inDir=$5
+
+# (String name, boolean retainFiles = false, String... files)
+function compileAndLink {
+    moduleName=$1
+    mkdir "$genDir"/out/"$moduleName"
+
+    args=""
+    for arg in "${@:4}"; do
+        if [[ $arg == res* ]]; then
+            args="$args $inDir/$arg"
+        else
+            args="$args $arg"
+        fi
+    done
+
+    $aapt2 compile -o "$genDir"/out/"$moduleName" $args
+
+    $aapt2 link \
+        -I "$FRAMEWORK_RES_APK" \
+        --manifest "$inDir"/"$3" \
+        -o "$genDir"/out/"$moduleName"/apk.apk \
+        "$genDir"/out/"$moduleName"/*.flat \
+        --no-compress
+
+    unzip -qq "$genDir"/out/"$moduleName"/apk.apk -d "$genDir"/out/"$moduleName"/unzip
+
+    if [[ "$2" == "APK_WITHOUT_FILE" || "$2" == "BOTH_WITHOUT_FILE" ]]; then
+        zip -q -d "$genDir"/out/"$moduleName"/apk.apk "res/*"
+        cp "$genDir"/out/"$moduleName"/apk.apk "$genDir"/output/raw/"$moduleName"Apk.apk
+    elif [[ "$2" == "APK" || "$2" == "BOTH" ]]; then
+        cp "$genDir"/out/"$moduleName"/apk.apk "$genDir"/output/raw/"$moduleName"Apk.apk
+    fi
+
+    if [[ "$2" == "ARSC" || "$2" == "BOTH" || "$2" == "BOTH_WITHOUT_FILE" ]]; then
+        zip -d "$genDir"/out/"$moduleName"/apk.apk "res/*"
+        cp "$genDir"/out/"$moduleName"/unzip/resources.arsc "$genDir"/output/raw/"$moduleName"Arsc.arsc
+    fi
+}
+
+rm -r "$genDir"/out
+rm -r "$genDir"/output
+rm -r "$genDir"/temp
+
+mkdir "$genDir"/out
+mkdir -p "$genDir"/output/raw
+mkdir -p "$genDir"/temp/res/drawable-nodpi
+mkdir -p "$genDir"/temp/res/layout
+
+compileAndLink stringOne BOTH AndroidManifestFramework.xml res/values/string_one.xml
+compileAndLink stringTwo BOTH AndroidManifestFramework.xml res/values/string_two.xml
+
+compileAndLink dimenOne BOTH AndroidManifestFramework.xml res/values/dimen_one.xml
+compileAndLink dimenTwo BOTH AndroidManifestFramework.xml res/values/dimen_two.xml
+
+compileAndLink drawableMdpiWithoutFile BOTH_WITHOUT_FILE AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png
+compileAndLink drawableMdpiWithFile APK AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png
+
+compileAndLink layoutWithoutFile BOTH_WITHOUT_FILE AndroidManifestFramework.xml res/values/activity_list_item_id.xml res/layout/activity_list_item.xml
+compileAndLink layoutWithFile APK AndroidManifestFramework.xml res/values/activity_list_item_id.xml res/layout/activity_list_item.xml
+
+cp -f "$inDir"/res/layout/layout_one.xml "$genDir"/temp/res/layout/layout.xml
+compileAndLink layoutOne ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
+cp -f "$genDir"/out/layoutOne/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutOne.xml
+
+cp -f "$inDir"/res/layout/layout_two.xml "$genDir"/temp/res/layout/layout.xml
+compileAndLink layoutTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
+cp -f "$genDir"/out/layoutTwo/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutTwo.xml
+
+drawableNoDpi="/res/drawable-nodpi"
+inDirDrawableNoDpi="$inDir$drawableNoDpi"
+
+cp -f "$inDirDrawableNoDpi"/nonAssetDrawableOne.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
+compileAndLink nonAssetDrawableOne ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
+cp -f "$genDir"/out/nonAssetDrawableOne/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableOne.xml
+
+cp -f "$inDirDrawableNoDpi"/nonAssetDrawableTwo.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
+compileAndLink nonAssetDrawableTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
+cp -f "$genDir"/out/nonAssetDrawableTwo/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableTwo.xml
+
+cp -f "$inDirDrawableNoDpi"/nonAssetBitmapGreen.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
+compileAndLink nonAssetBitmapGreen BOTH AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
+cp -f "$genDir"/out/nonAssetBitmapGreen/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapGreen.png
+
+cp -f "$inDirDrawableNoDpi"/nonAssetBitmapBlue.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
+compileAndLink nonAssetBitmapBlue ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
+cp -f "$genDir"/out/nonAssetBitmapBlue/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapBlue.png
+
+$soong_zip -o "$genDir"/out.zip -C "$genDir"/output/ -D "$genDir"/output/
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png b/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png
new file mode 100644
index 0000000..f3e53d7
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png
new file mode 100644
index 0000000..5231d17
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png
new file mode 100644
index 0000000..671d6d0
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml
new file mode 100644
index 0000000..f1a93d2
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<color
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="#A3C3E3"
+    />
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml
new file mode 100644
index 0000000..7c455a5
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<color
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="#3A3C3E"
+    />
diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml b/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml
new file mode 100644
index 0000000..d59059b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    />
+
diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml
new file mode 100644
index 0000000..ede3838
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    />
+
diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml
new file mode 100644
index 0000000..d8bff90
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    />
+
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml
new file mode 100644
index 0000000..a552431
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<resources>
+    <public type="layout" name="activity_list_item" id="0x01090000" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml
new file mode 100644
index 0000000..69ecf23
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<resources>
+    <public type="dimen" name="app_icon_size" id="0x01050000" />
+    <dimen name="app_icon_size">564716dp</dimen>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml
new file mode 100644
index 0000000..4d55def
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<resources>
+    <public type="dimen" name="app_icon_size" id="0x01050000" />
+    <dimen name="app_icon_size">565717dp</dimen>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml
new file mode 100644
index 0000000..b5b4dfd
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<resources>
+    <public type="drawable" name="ic_delete" id="0x0108001d" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml
new file mode 100644
index 0000000..4962a07
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <public type="layout" name="layout" id="0x7f020000" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml
new file mode 100644
index 0000000..38b152b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <public type="drawable" name="non_asset_bitmap" id="0x7f010000" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml
new file mode 100644
index 0000000..bdd6f58
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <public type="drawable" name="non_asset_drawable" id="0x7f010001" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml
new file mode 100644
index 0000000..4fc5272
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<resources>
+    <public type="string" name="cancel" id="0x01040000" />
+    <string name="cancel">SomeRidiculouslyUnlikelyStringOne</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml
new file mode 100644
index 0000000..3604d7b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<resources>
+    <public type="string" name="cancel" id="0x01040000" />
+    <string name="cancel">SomeRidiculouslyUnlikelyStringTwo</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/splits/Android.bp b/core/tests/ResourceLoaderTests/splits/Android.bp
new file mode 100644
index 0000000..4582808
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/splits/Android.bp
@@ -0,0 +1,19 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "FrameworksResourceLoaderTestsSplitTwo"
+}
diff --git a/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml
new file mode 100644
index 0000000..aad8c27
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.res.loader.test"
+    split="split_two"
+    >
+
+    <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+    <application android:hasCode="false" />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml b/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml
new file mode 100644
index 0000000..a367063
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <public type="string" name="split_overlaid" id="0x7f040001" />
+    <string name="split_overlaid">Split TWO Overlaid</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt
new file mode 100644
index 0000000..b1bdc96
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.content.res.loader.test
+
+import android.content.res.loader.DirectoryResourceLoader
+import android.content.res.loader.ResourceLoader
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import java.io.File
+
+class DirectoryResourceLoaderTest : ResourceLoaderTestBase() {
+
+    @get:Rule
+    val testName = TestName()
+
+    private lateinit var testDir: File
+    private lateinit var loader: ResourceLoader
+
+    @Before
+    fun setUpTestDir() {
+        testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}")
+        loader = DirectoryResourceLoader(testDir)
+    }
+
+    @After
+    fun deleteTestFiles() {
+        testDir.deleteRecursively()
+    }
+
+    @Test
+    fun loadDrawableXml() {
+        "nonAssetDrawableOne" writeTo "res/drawable-nodpi-v4/non_asset_drawable.xml"
+        val provider = openArsc("nonAssetDrawableOne")
+
+        fun getValue() = (resources.getDrawable(R.drawable.non_asset_drawable) as ColorDrawable)
+                .color
+
+        assertThat(getValue()).isEqualTo(Color.parseColor("#B2D2F2"))
+
+        addLoader(loader to provider)
+
+        assertThat(getValue()).isEqualTo(Color.parseColor("#A3C3E3"))
+    }
+
+    @Test
+    fun loadDrawableBitmap() {
+        "nonAssetBitmapGreen" writeTo "res/drawable-nodpi-v4/non_asset_bitmap.png"
+        val provider = openArsc("nonAssetBitmapGreen")
+
+        fun getValue() = (resources.getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable)
+                .bitmap.getColor(0, 0).toArgb()
+
+        assertThat(getValue()).isEqualTo(Color.RED)
+
+        addLoader(loader to provider)
+
+        assertThat(getValue()).isEqualTo(Color.GREEN)
+    }
+
+    @Test
+    fun loadXml() {
+        "layoutOne" writeTo "res/layout/layout.xml"
+        val provider = openArsc("layoutOne")
+
+        fun getValue() = resources.getLayout(R.layout.layout).advanceToRoot().name
+
+        assertThat(getValue()).isEqualTo("FrameLayout")
+
+        addLoader(loader to provider)
+
+        assertThat(getValue()).isEqualTo("RelativeLayout")
+    }
+
+    private infix fun String.writeTo(path: String) {
+        val testFile = testDir.resolve(path)
+        testFile.parentFile!!.mkdirs()
+        resources.openRawResource(rawFile(this))
+                .copyTo(testFile.outputStream())
+    }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt
new file mode 100644
index 0000000..a6a8378
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.content.res.loader.test
+
+import android.content.res.AssetManager
+import android.content.res.loader.DirectoryResourceLoader
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.mock
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.nio.file.Paths
+
+@RunWith(Parameterized::class)
+class ResourceLoaderAssetTest : ResourceLoaderTestBase() {
+
+    companion object {
+        private const val BASE_TEST_PATH = "android/content/res/loader/test/file.txt"
+        private const val TEST_TEXT = "some text"
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun parameters(): Array<Array<out Any?>> {
+            val fromInputStream: ResourceLoader.(String) -> Any? = {
+                loadAsset(eq(it), anyInt())
+            }
+
+            val fromFileDescriptor: ResourceLoader.(String) -> Any? = {
+                loadAssetFd(eq(it))
+            }
+
+            val openAsset: AssetManager.() -> String? = {
+                open(BASE_TEST_PATH).reader().readText()
+            }
+
+            val openNonAsset: AssetManager.() -> String? = {
+                openNonAssetFd(BASE_TEST_PATH).readText()
+            }
+
+            return arrayOf(
+                    arrayOf("assets", fromInputStream, openAsset),
+                    arrayOf("", fromFileDescriptor, openNonAsset)
+            )
+        }
+    }
+
+    @get:Rule
+    val testName = TestName()
+
+    @JvmField
+    @field:Parameterized.Parameter(0)
+    var prefix: String? = null
+
+    @field:Parameterized.Parameter(1)
+    lateinit var loadAssetFunction: ResourceLoader.(String) -> Any?
+
+    @field:Parameterized.Parameter(2)
+    lateinit var openAssetFunction: AssetManager.() -> String?
+
+    private val testPath: String
+        get() = Paths.get(prefix.orEmpty(), BASE_TEST_PATH).toString()
+
+    private fun ResourceLoader.loadAsset() = loadAssetFunction(testPath)
+
+    private fun AssetManager.openAsset() = openAssetFunction()
+
+    private lateinit var testDir: File
+
+    @Before
+    fun setUpTestDir() {
+        testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}")
+        testDir.resolve(testPath).apply { parentFile!!.mkdirs() }.writeText(TEST_TEXT)
+    }
+
+    @Test
+    fun multipleLoadersSearchesBackwards() {
+        // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it
+        val loader = DirectoryResourceLoader(testDir)
+        val loaderWrapper = mock(ResourceLoader::class.java).apply {
+            doAnswer { loader.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) }
+                    .`when`(this).loadAsset(anyString(), anyInt())
+            doAnswer { loader.loadAssetFd(it.arguments[0] as String) }
+                    .`when`(this).loadAssetFd(anyString())
+        }
+
+        val one = loaderWrapper to ResourcesProvider.empty()
+        val two = mockLoader {
+            doReturn(null).`when`(it).loadAsset()
+        }
+
+        addLoader(one, two)
+
+        assertOpenedAsset()
+        inOrder(two.first, one.first).apply {
+            verify(two.first).loadAsset()
+            verify(one.first).loadAsset()
+        }
+    }
+
+    @Test(expected = FileNotFoundException::class)
+    fun failToFindThrowsFileNotFound() {
+        val one = mockLoader {
+            doReturn(null).`when`(it).loadAsset()
+        }
+        val two = mockLoader {
+            doReturn(null).`when`(it).loadAsset()
+        }
+
+        addLoader(one, two)
+
+        assertOpenedAsset()
+    }
+
+    @Test
+    fun throwingIOExceptionIsSkipped() {
+        val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty()
+        val two = mockLoader {
+            doAnswer { throw IOException() }.`when`(it).loadAsset()
+        }
+
+        addLoader(one, two)
+
+        assertOpenedAsset()
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun throwingNonIOExceptionCausesFailure() {
+        val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty()
+        val two = mockLoader {
+            doAnswer { throw IllegalStateException() }.`when`(it).loadAsset()
+        }
+
+        addLoader(one, two)
+
+        assertOpenedAsset()
+    }
+
+    private fun assertOpenedAsset() {
+        assertThat(resources.assets.openAsset()).isEqualTo(TEST_TEXT)
+    }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt
new file mode 100644
index 0000000..e01e254
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt
@@ -0,0 +1,236 @@
+/*
+ * 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.content.res.loader.test
+
+import android.app.Activity
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Color
+import android.os.Bundle
+import android.os.ParcelFileDescriptor
+import android.widget.FrameLayout
+import androidx.test.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
+import androidx.test.runner.lifecycle.Stage
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.util.Arrays
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import java.util.concurrent.FutureTask
+import java.util.concurrent.TimeUnit
+
+// @Ignore("UiAutomation is crashing with not connected, not sure why")
+@RunWith(Parameterized::class)
+class ResourceLoaderChangesTest : ResourceLoaderTestBase() {
+
+    companion object {
+        private const val TIMEOUT = 30L
+        private const val OVERLAY_PACKAGE = "android.content.res.loader.test.overlay"
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun data() = arrayOf(DataType.APK, DataType.ARSC)
+    }
+
+    @field:Parameterized.Parameter(0)
+    override lateinit var dataType: DataType
+
+    @get:Rule
+    val activityRule: ActivityTestRule<TestActivity> =
+            ActivityTestRule<TestActivity>(TestActivity::class.java, false, true)
+
+    // Redirect to the Activity's resources
+    override val resources: Resources
+        get() = activityRule.getActivity().resources
+
+    private val activity: TestActivity
+        get() = activityRule.getActivity()
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    @Before
+    @After
+    fun disableOverlay() {
+//        enableOverlay(OVERLAY_PACKAGE, false)
+    }
+
+    @Test
+    fun activityRecreate() = verifySameBeforeAndAfter {
+        val oldActivity = activity
+        var newActivity: Activity? = null
+        instrumentation.runOnMainSync { oldActivity.recreate() }
+        instrumentation.waitForIdleSync()
+        instrumentation.runOnMainSync {
+            newActivity = ActivityLifecycleMonitorRegistry.getInstance()
+                    .getActivitiesInStage(Stage.RESUMED)
+                    .single()
+        }
+
+        assertThat(newActivity).isNotNull()
+        assertThat(newActivity).isNotSameAs(oldActivity)
+
+        // Return the new resources to assert on
+        return@verifySameBeforeAndAfter newActivity!!.resources
+    }
+
+    @Test
+    fun activityHandledOrientationChange() = verifySameBeforeAndAfter {
+        val latch = CountDownLatch(1)
+        val oldConfig = Configuration().apply { setTo(resources.configuration) }
+        var changedConfig: Configuration? = null
+
+        activity.callback = object : TestActivity.Callback {
+            override fun onConfigurationChanged(newConfig: Configuration) {
+                changedConfig = newConfig
+                latch.countDown()
+            }
+        }
+
+        val isPortrait = resources.displayMetrics.run { widthPixels < heightPixels }
+        val newRotation = if (isPortrait) {
+            UiAutomation.ROTATION_FREEZE_90
+        } else {
+            UiAutomation.ROTATION_FREEZE_0
+        }
+
+        instrumentation.uiAutomation.setRotation(newRotation)
+
+        assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue()
+        assertThat(changedConfig).isNotEqualTo(oldConfig)
+        return@verifySameBeforeAndAfter activity.resources
+    }
+
+    @Test
+    fun enableOverlayCausingPathChange() = verifySameBeforeAndAfter {
+        assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Not overlaid")
+
+        enableOverlay(OVERLAY_PACKAGE, true)
+
+        assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Overlaid")
+
+        return@verifySameBeforeAndAfter activity.resources
+    }
+
+    @Test
+    fun enableOverlayChildContextUnaffected() {
+        val childContext = activity.createConfigurationContext(Configuration())
+        val childResources = childContext.resources
+        val originalValue = childResources.getString(android.R.string.cancel)
+        assertThat(childResources.getString(R.string.loader_path_change_test))
+                .isEqualTo("Not overlaid")
+
+        verifySameBeforeAndAfter {
+            enableOverlay(OVERLAY_PACKAGE, true)
+            return@verifySameBeforeAndAfter activity.resources
+        }
+
+        // Loader not applied, but overlay change propagated
+        assertThat(childResources.getString(android.R.string.cancel)).isEqualTo(originalValue)
+        assertThat(childResources.getString(R.string.loader_path_change_test))
+                .isEqualTo("Overlaid")
+    }
+
+    // All these tests assert for the exact same loaders/values, so extract that logic out
+    private fun verifySameBeforeAndAfter(block: () -> Resources) {
+        // TODO(chiuwinson): atest doesn't work with @Ignore, UiAutomation not connected error
+        Assume.assumeFalse(true)
+
+        val originalValue = resources.getString(android.R.string.cancel)
+
+        val loader = "stringOne".openLoader()
+        addLoader(loader)
+
+        val oldLoaders = resources.loaders
+        val oldValue = resources.getString(android.R.string.cancel)
+
+        assertThat(oldValue).isNotEqualTo(originalValue)
+
+        val newResources = block()
+
+        val newLoaders = newResources.loaders
+        val newValue = newResources.getString(android.R.string.cancel)
+
+        assertThat(newValue).isEqualTo(oldValue)
+        assertThat(newLoaders).isEqualTo(oldLoaders)
+    }
+
+    // Copied from overlaytests LocalOverlayManager
+    private fun enableOverlay(packageName: String, enable: Boolean) {
+        val executor = Executor { Thread(it).start() }
+        val pattern = (if (enable) "[x]" else "[ ]") + " " + packageName
+        if (executeShellCommand("cmd overlay list").contains(pattern)) {
+            // nothing to do, overlay already in the requested state
+            return
+        }
+
+        val oldApkPaths = resources.assets.apkPaths
+        val task = FutureTask {
+            while (true) {
+                if (!Arrays.equals(oldApkPaths, resources.assets.apkPaths)) {
+                    return@FutureTask true
+                }
+                Thread.sleep(10)
+            }
+
+            @Suppress("UNREACHABLE_CODE")
+            return@FutureTask false
+        }
+
+        val command = if (enable) "enable" else "disable"
+        executeShellCommand("cmd overlay $command $packageName")
+        executor.execute(task)
+        assertThat(task.get(TIMEOUT, TimeUnit.SECONDS)).isTrue()
+    }
+
+    private fun executeShellCommand(command: String): String {
+        val uiAutomation = instrumentation.uiAutomation
+        val pfd = uiAutomation.executeShellCommand(command)
+        return ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.reader().readText() }
+    }
+}
+
+class TestActivity : Activity() {
+
+    var callback: Callback? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        setContentView(FrameLayout(this).apply {
+            setBackgroundColor(Color.BLUE)
+        })
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        callback?.onConfigurationChanged(newConfig)
+    }
+
+    interface Callback {
+        fun onConfigurationChanged(newConfig: Configuration)
+    }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt
new file mode 100644
index 0000000..09fd27e
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt
@@ -0,0 +1,183 @@
+/*
+ * 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.content.res.loader.test
+
+import android.content.res.Resources
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import com.google.common.truth.Truth.assertThat
+import org.hamcrest.CoreMatchers.not
+import org.junit.Assume.assumeThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(Parameterized::class)
+class ResourceLoaderDrawableTest : ResourceLoaderTestBase() {
+
+    companion object {
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun data() = arrayOf(DataType.APK, DataType.ARSC)
+    }
+
+    @field:Parameterized.Parameter(0)
+    override lateinit var dataType: DataType
+
+    @Test
+    fun matchingConfig() {
+        val original = getDrawable(android.R.drawable.ic_delete)
+        val loader = "drawableMdpiWithoutFile".openLoader()
+        `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any()))
+                .thenReturn(ColorDrawable(Color.BLUE))
+
+        addLoader(loader)
+
+        updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+        val drawable = getDrawable(android.R.drawable.ic_delete)
+
+        loader.verifyLoadDrawableCalled()
+
+        assertThat(drawable).isNotEqualTo(original)
+        assertThat(drawable).isInstanceOf(ColorDrawable::class.java)
+        assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE)
+    }
+
+    @Test
+    fun worseConfig() {
+        val loader = "drawableMdpiWithoutFile".openLoader()
+        addLoader(loader)
+
+        updateConfiguration { densityDpi = 480 /* xhdpi */ }
+
+        getDrawable(android.R.drawable.ic_delete)
+
+        verify(loader.first, never()).loadDrawable(any(), anyInt(), anyInt(), any())
+    }
+
+    @Test
+    fun multipleLoaders() {
+        val original = getDrawable(android.R.drawable.ic_delete)
+        val loaderOne = "drawableMdpiWithoutFile".openLoader()
+        val loaderTwo = "drawableMdpiWithoutFile".openLoader()
+
+        `when`(loaderTwo.first.loadDrawable(any(), anyInt(), anyInt(), any()))
+                .thenReturn(ColorDrawable(Color.BLUE))
+
+        addLoader(loaderOne, loaderTwo)
+
+        updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+        val drawable = getDrawable(android.R.drawable.ic_delete)
+        loaderOne.verifyLoadDrawableNotCalled()
+        loaderTwo.verifyLoadDrawableCalled()
+
+        assertThat(drawable).isNotEqualTo(original)
+        assertThat(drawable).isInstanceOf(ColorDrawable::class.java)
+        assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE)
+    }
+
+    @Test(expected = Resources.NotFoundException::class)
+    fun multipleLoadersNoReturnWithoutFile() {
+        val loaderOne = "drawableMdpiWithoutFile".openLoader()
+        val loaderTwo = "drawableMdpiWithoutFile".openLoader()
+
+        addLoader(loaderOne, loaderTwo)
+
+        updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+        try {
+            getDrawable(android.R.drawable.ic_delete)
+        } finally {
+            // We expect the call to fail because at least the loader won't resolve the overridden
+            // drawable, but we should still verify that both loaders were called before allowing
+            // the exception to propagate.
+            loaderOne.verifyLoadDrawableNotCalled()
+            loaderTwo.verifyLoadDrawableCalled()
+        }
+    }
+
+    @Test
+    fun multipleLoadersReturnWithFile() {
+        // Can't return a file if an ARSC
+        assumeThat(dataType, not(DataType.ARSC))
+
+        val original = getDrawable(android.R.drawable.ic_delete)
+        val loaderOne = "drawableMdpiWithFile".openLoader()
+        val loaderTwo = "drawableMdpiWithFile".openLoader()
+
+        addLoader(loaderOne, loaderTwo)
+
+        updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+        val drawable = getDrawable(android.R.drawable.ic_delete)
+        loaderOne.verifyLoadDrawableNotCalled()
+        loaderTwo.verifyLoadDrawableCalled()
+
+        assertThat(drawable).isNotNull()
+        assertThat(drawable).isInstanceOf(original.javaClass)
+    }
+
+    @Test
+    fun unhandledResourceIgnoresLoaders() {
+        val loader = "drawableMdpiWithoutFile".openLoader()
+        `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any()))
+                .thenReturn(ColorDrawable(Color.BLUE))
+        addLoader(loader)
+
+        getDrawable(android.R.drawable.ic_menu_add)
+
+        loader.verifyLoadDrawableNotCalled()
+
+        getDrawable(android.R.drawable.ic_delete)
+
+        loader.verifyLoadDrawableCalled()
+    }
+
+    private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableCalled() {
+        verify(first).loadDrawable(
+                argThat {
+                    it.density == 160 &&
+                            it.resourceId == android.R.drawable.ic_delete &&
+                            it.string == "res/drawable-mdpi-v4/ic_delete.png"
+                },
+                eq(android.R.drawable.ic_delete),
+                eq(0),
+                any()
+        )
+    }
+
+    private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableNotCalled() {
+        verify(first, never()).loadDrawable(
+                any(),
+                anyInt(),
+                anyInt(),
+                any()
+        )
+    }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt
new file mode 100644
index 0000000..1ec2094
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.content.res.loader.test
+
+import android.content.res.Resources
+import android.content.res.XmlResourceParser
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import com.google.common.truth.Truth.assertThat
+import org.hamcrest.CoreMatchers.not
+import org.junit.Assume.assumeThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(Parameterized::class)
+class ResourceLoaderLayoutTest : ResourceLoaderTestBase() {
+
+    companion object {
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun data() = arrayOf(DataType.APK, DataType.ARSC)
+    }
+
+    @field:Parameterized.Parameter(0)
+    override lateinit var dataType: DataType
+
+    @Test
+    fun singleLoader() {
+        val original = getLayout(android.R.layout.activity_list_item)
+        val mockXml = mock(XmlResourceParser::class.java)
+        val loader = "layoutWithoutFile".openLoader()
+        `when`(loader.first.loadXmlResourceParser(any(), anyInt()))
+                .thenReturn(mockXml)
+
+        addLoader(loader)
+
+        val layout = getLayout(android.R.layout.activity_list_item)
+        loader.verifyLoadLayoutCalled()
+
+        assertThat(layout).isNotEqualTo(original)
+        assertThat(layout).isSameAs(mockXml)
+    }
+
+    @Test
+    fun multipleLoaders() {
+        val original = getLayout(android.R.layout.activity_list_item)
+        val loaderOne = "layoutWithoutFile".openLoader()
+        val loaderTwo = "layoutWithoutFile".openLoader()
+
+        val mockXml = mock(XmlResourceParser::class.java)
+        `when`(loaderTwo.first.loadXmlResourceParser(any(), anyInt()))
+                .thenReturn(mockXml)
+
+        addLoader(loaderOne, loaderTwo)
+
+        val layout = getLayout(android.R.layout.activity_list_item)
+        loaderOne.verifyLoadLayoutNotCalled()
+        loaderTwo.verifyLoadLayoutCalled()
+
+        assertThat(layout).isNotEqualTo(original)
+        assertThat(layout).isSameAs(mockXml)
+    }
+
+    @Test(expected = Resources.NotFoundException::class)
+    fun multipleLoadersNoReturnWithoutFile() {
+        val loaderOne = "layoutWithoutFile".openLoader()
+        val loaderTwo = "layoutWithoutFile".openLoader()
+
+        addLoader(loaderOne, loaderTwo)
+
+        try {
+            getLayout(android.R.layout.activity_list_item)
+        } finally {
+            // We expect the call to fail because at least one loader must resolve the overridden
+            // layout, but we should still verify that both loaders were called before allowing
+            // the exception to propagate.
+            loaderOne.verifyLoadLayoutNotCalled()
+            loaderTwo.verifyLoadLayoutCalled()
+        }
+    }
+
+    @Test
+    fun multipleLoadersReturnWithFile() {
+        // Can't return a file if an ARSC
+        assumeThat(dataType, not(DataType.ARSC))
+
+        val loaderOne = "layoutWithFile".openLoader()
+        val loaderTwo = "layoutWithFile".openLoader()
+
+        addLoader(loaderOne, loaderTwo)
+
+        val xml = getLayout(android.R.layout.activity_list_item)
+        loaderOne.verifyLoadLayoutNotCalled()
+        loaderTwo.verifyLoadLayoutCalled()
+
+        assertThat(xml).isNotNull()
+    }
+
+    @Test
+    fun unhandledResourceIgnoresLoaders() {
+        val loader = "layoutWithoutFile".openLoader()
+        val mockXml = mock(XmlResourceParser::class.java)
+        `when`(loader.first.loadXmlResourceParser(any(), anyInt()))
+                .thenReturn(mockXml)
+        addLoader(loader)
+
+        getLayout(android.R.layout.preference_category)
+
+        verify(loader.first, never())
+                .loadXmlResourceParser(anyString(), anyInt())
+
+        getLayout(android.R.layout.activity_list_item)
+
+        loader.verifyLoadLayoutCalled()
+    }
+
+    private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutCalled() {
+        verify(first).loadXmlResourceParser(
+                "res/layout/activity_list_item.xml",
+                        android.R.layout.activity_list_item
+        )
+    }
+
+    private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutNotCalled() {
+        verify(first, never()).loadXmlResourceParser(
+                anyString(),
+                anyInt()
+        )
+    }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
new file mode 100644
index 0000000..5af453d
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
@@ -0,0 +1,226 @@
+/*
+ * 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.content.res.loader.test
+
+import android.content.Context
+import android.content.res.AssetManager
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import android.os.ParcelFileDescriptor
+import android.util.TypedValue
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
+import androidx.test.InstrumentationRegistry
+import org.junit.After
+import org.junit.Before
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import java.io.Closeable
+
+abstract class ResourceLoaderTestBase {
+
+    open lateinit var dataType: DataType
+
+    protected lateinit var context: Context
+    protected open val resources: Resources
+        get() = context.resources
+    protected open val assets: AssetManager
+        get() = resources.assets
+
+    // Track opened streams and ResourcesProviders to close them after testing
+    private val openedObjects = mutableListOf<Closeable>()
+
+    @Before
+    fun setUpBase() {
+        context = InstrumentationRegistry.getTargetContext()
+    }
+
+    @After
+    fun removeAllLoaders() {
+        resources.setLoaders(null)
+        openedObjects.forEach {
+            try {
+                it.close()
+            } catch (ignored: Exception) {
+            }
+        }
+    }
+
+    protected fun getString(@StringRes stringRes: Int, debugLog: Boolean = false) =
+            logResolution(debugLog) { getString(stringRes) }
+
+    protected fun getDrawable(@DrawableRes drawableRes: Int, debugLog: Boolean = false) =
+            logResolution(debugLog) { getDrawable(drawableRes) }
+
+    protected fun getLayout(@LayoutRes layoutRes: Int, debugLog: Boolean = false) =
+            logResolution(debugLog) { getLayout(layoutRes) }
+
+    protected fun getDimensionPixelSize(@DimenRes dimenRes: Int, debugLog: Boolean = false) =
+            logResolution(debugLog) { getDimensionPixelSize(dimenRes) }
+
+    private fun <T> logResolution(debugLog: Boolean = false, block: Resources.() -> T): T {
+        if (debugLog) {
+            resources.assets.setResourceResolutionLoggingEnabled(true)
+        }
+
+        var thrown = false
+
+        try {
+            return resources.block()
+        } catch (t: Throwable) {
+            // No good way to log to test output other than throwing an exception
+            if (debugLog) {
+                thrown = true
+                throw IllegalStateException(resources.assets.lastResourceResolution, t)
+            } else {
+                throw t
+            }
+        } finally {
+            if (!thrown && debugLog) {
+                throw IllegalStateException(resources.assets.lastResourceResolution)
+            }
+        }
+    }
+
+    protected fun updateConfiguration(block: Configuration.() -> Unit) {
+        val configuration = Configuration().apply {
+            setTo(resources.configuration)
+            block()
+        }
+
+        resources.updateConfiguration(configuration, resources.displayMetrics)
+    }
+
+    protected fun String.openLoader(
+        dataType: DataType = this@ResourceLoaderTestBase.dataType
+    ): Pair<ResourceLoader, ResourcesProvider> = when (dataType) {
+        DataType.APK -> {
+            mock(ResourceLoader::class.java) to context.copiedRawFile("${this}Apk").use {
+                ResourcesProvider.loadFromApk(it)
+            }.also { openedObjects += it }
+        }
+        DataType.ARSC -> {
+            mock(ResourceLoader::class.java) to openArsc(this)
+        }
+        DataType.SPLIT -> {
+            mock(ResourceLoader::class.java) to ResourcesProvider.loadFromSplit(context, this)
+        }
+        DataType.ASSET -> mockLoader {
+            doAnswer { byteInputStream() }.`when`(it)
+                    .loadAsset(eq("assets/Asset.txt"), anyInt())
+        }
+        DataType.ASSET_FD -> mockLoader {
+            doAnswer {
+                val file = context.filesDir.resolve("Asset.txt")
+                file.writeText(this)
+                ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
+            }.`when`(it).loadAssetFd("assets/Asset.txt")
+        }
+        DataType.NON_ASSET -> mockLoader {
+            doAnswer {
+                val file = context.filesDir.resolve("NonAsset.txt")
+                file.writeText(this)
+                ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
+            }.`when`(it).loadAssetFd("NonAsset.txt")
+        }
+        DataType.NON_ASSET_DRAWABLE -> mockLoader(openArsc(this)) {
+            doReturn(null).`when`(it).loadDrawable(argThat { value ->
+                value.type == TypedValue.TYPE_STRING &&
+                        value.resourceId == 0x7f010001 &&
+                        value.string == "res/drawable-nodpi-v4/non_asset_drawable.xml"
+            }, eq(0x7f010001), anyInt(), ArgumentMatchers.any())
+
+            doAnswer { context.copiedRawFile(this) }.`when`(it)
+                    .loadAssetFd("res/drawable-nodpi-v4/non_asset_drawable.xml")
+        }
+        DataType.NON_ASSET_BITMAP -> mockLoader(openArsc(this)) {
+            doReturn(null).`when`(it).loadDrawable(argThat { value ->
+                value.type == TypedValue.TYPE_STRING &&
+                        value.resourceId == 0x7f010000 &&
+                        value.string == "res/drawable-nodpi-v4/non_asset_bitmap.png"
+            }, eq(0x7f010000), anyInt(), ArgumentMatchers.any())
+
+            doAnswer { resources.openRawResourceFd(rawFile(this)).createInputStream() }
+                    .`when`(it)
+                    .loadAsset(eq("res/drawable-nodpi-v4/non_asset_bitmap.png"), anyInt())
+        }
+        DataType.NON_ASSET_LAYOUT -> mockLoader(openArsc(this)) {
+            doReturn(null).`when`(it)
+                    .loadXmlResourceParser("res/layout/layout.xml", 0x7f020000)
+
+            doAnswer { context.copiedRawFile(this) }.`when`(it)
+                    .loadAssetFd("res/layout/layout.xml")
+        }
+    }
+
+    protected fun mockLoader(
+        provider: ResourcesProvider = ResourcesProvider.empty(),
+        block: (ResourceLoader) -> Unit = {}
+    ): Pair<ResourceLoader, ResourcesProvider> {
+        return mock(ResourceLoader::class.java, Utils.ANSWER_THROWS)
+                .apply(block) to provider
+    }
+
+    protected fun openArsc(rawName: String): ResourcesProvider {
+        return context.copiedRawFile("${rawName}Arsc")
+                .use { ResourcesProvider.loadFromArsc(it) }
+                .also { openedObjects += it }
+    }
+
+    // This specifically uses addLoader so both behaviors are tested
+    protected fun addLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
+        pairs.forEach { resources.addLoader(it.first, it.second) }
+    }
+
+    protected fun setLoaders(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
+        resources.setLoaders(pairs.map { android.util.Pair(it.first, it.second) })
+    }
+
+    protected fun addLoader(pair: Pair<out ResourceLoader, ResourcesProvider>, index: Int) {
+        resources.addLoader(pair.first, pair.second, index)
+    }
+
+    protected fun removeLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
+        pairs.forEach { resources.removeLoader(it.first) }
+    }
+
+    protected fun getLoaders(): MutableList<Pair<ResourceLoader, ResourcesProvider>> {
+        // Cast instead of toMutableList to maintain the same object
+        return resources.getLoaders() as MutableList<Pair<ResourceLoader, ResourcesProvider>>
+    }
+
+    enum class DataType {
+        APK,
+        ARSC,
+        SPLIT,
+        ASSET,
+        ASSET_FD,
+        NON_ASSET,
+        NON_ASSET_DRAWABLE,
+        NON_ASSET_BITMAP,
+        NON_ASSET_LAYOUT,
+    }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
new file mode 100644
index 0000000..017552a
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
@@ -0,0 +1,354 @@
+/*
+ * 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.content.res.loader.test
+
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Tests generic ResourceLoader behavior. Intentionally abstract in its test methodology because
+ * the behavior being verified isn't specific to any resource type. As long as it can pass an
+ * equals check.
+ *
+ * Currently tests strings and dimens since String and any Number seemed most relevant to verify.
+ */
+@RunWith(Parameterized::class)
+class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
+
+    companion object {
+        @Parameterized.Parameters(name = "{1} {0}")
+        @JvmStatic
+        fun parameters(): Array<Any> {
+            val parameters = mutableListOf<Parameter>()
+
+            // R.string
+            parameters += Parameter(
+                    { getString(android.R.string.cancel) },
+                    "stringOne", { "SomeRidiculouslyUnlikelyStringOne" },
+                    "stringTwo", { "SomeRidiculouslyUnlikelyStringTwo" },
+                    listOf(DataType.APK, DataType.ARSC)
+            )
+
+            // R.dimen
+            parameters += Parameter(
+                    { resources.getDimensionPixelSize(android.R.dimen.app_icon_size) },
+                    "dimenOne", { 564716.dpToPx(resources) },
+                    "dimenTwo", { 565717.dpToPx(resources) },
+                    listOf(DataType.APK, DataType.ARSC)
+            )
+
+            // File in the assets directory
+            parameters += Parameter(
+                    { assets.open("Asset.txt").reader().readText() },
+                    "assetOne", { "assetOne" },
+                    "assetTwo", { "assetTwo" },
+                    listOf(DataType.ASSET)
+            )
+
+            // From assets directory returning file descriptor
+            parameters += Parameter(
+                    { assets.openFd("Asset.txt").readText() },
+                    "assetOne", { "assetOne" },
+                    "assetTwo", { "assetTwo" },
+                    listOf(DataType.ASSET_FD)
+            )
+
+            // From root directory returning file descriptor
+            parameters += Parameter(
+                    { assets.openNonAssetFd("NonAsset.txt").readText() },
+                    "NonAssetOne", { "NonAssetOne" },
+                    "NonAssetTwo", { "NonAssetTwo" },
+                    listOf(DataType.NON_ASSET)
+            )
+
+            // Asset as compiled XML drawable
+            parameters += Parameter(
+                    { (getDrawable(R.drawable.non_asset_drawable) as ColorDrawable).color },
+                    "nonAssetDrawableOne", { Color.parseColor("#A3C3E3") },
+                    "nonAssetDrawableTwo", { Color.parseColor("#3A3C3E") },
+                    listOf(DataType.NON_ASSET_DRAWABLE)
+            )
+
+            // Asset as compiled bitmap drawable
+            parameters += Parameter(
+                    {
+                        (getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable)
+                                .bitmap.getColor(0, 0).toArgb()
+                    },
+                    "nonAssetBitmapGreen", { Color.GREEN },
+                    "nonAssetBitmapBlue", { Color.BLUE },
+                    listOf(DataType.NON_ASSET_BITMAP)
+            )
+
+            // Asset as compiled XML layout
+            parameters += Parameter(
+                    { getLayout(R.layout.layout).advanceToRoot().name },
+                    "layoutOne", { "RelativeLayout" },
+                    "layoutTwo", { "LinearLayout" },
+                    listOf(DataType.NON_ASSET_LAYOUT)
+            )
+
+            // Isolated resource split
+            parameters += Parameter(
+                    { getString(R.string.split_overlaid) },
+                    "split_one", { "Split ONE Overlaid" },
+                    "split_two", { "Split TWO Overlaid" },
+                    listOf(DataType.SPLIT)
+            )
+
+            return parameters.flatMap { parameter ->
+                parameter.dataTypes.map { dataType ->
+                    arrayOf(dataType, parameter)
+                }
+            }.toTypedArray()
+        }
+    }
+
+    @Suppress("LateinitVarOverridesLateinitVar")
+    @field:Parameterized.Parameter(0)
+    override lateinit var dataType: DataType
+
+    @field:Parameterized.Parameter(1)
+    lateinit var parameter: Parameter
+
+    private val valueOne by lazy { parameter.valueOne(this) }
+    private val valueTwo by lazy { parameter.valueTwo(this) }
+
+    private fun openOne() = parameter.loaderOne.openLoader()
+    private fun openTwo() = parameter.loaderTwo.openLoader()
+
+    // Class method for syntax highlighting purposes
+    private fun getValue() = parameter.getValue(this)
+
+    @Test
+    fun verifyValueUniqueness() {
+        // Ensure the parameters are valid in case of coding errors
+        assertNotEquals(valueOne, getValue())
+        assertNotEquals(valueTwo, getValue())
+        assertNotEquals(valueOne, valueTwo)
+    }
+
+    @Test
+    fun addMultipleLoaders() {
+        val originalValue = getValue()
+        val testOne = openOne()
+        val testTwo = openTwo()
+
+        addLoader(testOne, testTwo)
+
+        assertEquals(valueTwo, getValue())
+
+        removeLoader(testTwo)
+
+        assertEquals(valueOne, getValue())
+
+        removeLoader(testOne)
+
+        assertEquals(originalValue, getValue())
+    }
+
+    @Test
+    fun setMultipleLoaders() {
+        val originalValue = getValue()
+        val testOne = openOne()
+        val testTwo = openTwo()
+
+        setLoaders(testOne, testTwo)
+
+        assertEquals(valueTwo, getValue())
+
+        removeLoader(testTwo)
+
+        assertEquals(valueOne, getValue())
+
+        setLoaders()
+
+        assertEquals(originalValue, getValue())
+    }
+
+    @Test
+    fun getLoadersContainsAll() {
+        val testOne = openOne()
+        val testTwo = openTwo()
+
+        addLoader(testOne, testTwo)
+
+        assertThat(getLoaders()).containsAllOf(testOne, testTwo)
+    }
+
+    @Test
+    fun getLoadersDoesNotLeakMutability() {
+        val originalValue = getValue()
+        val testOne = openOne()
+        val testTwo = openTwo()
+
+        addLoader(testOne)
+
+        assertEquals(valueOne, getValue())
+
+        val loaders = getLoaders()
+        loaders += testTwo
+
+        assertEquals(valueOne, getValue())
+
+        removeLoader(testOne)
+
+        assertEquals(originalValue, getValue())
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun alreadyAddedThrows() {
+        val testOne = openOne()
+        val testTwo = openTwo()
+
+        addLoader(testOne)
+        addLoader(testTwo)
+        addLoader(testOne)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun alreadyAddedAndSetThrows() {
+        val testOne = openOne()
+        val testTwo = openTwo()
+
+        addLoader(testOne)
+        addLoader(testTwo)
+        setLoaders(testTwo)
+    }
+
+    @Test
+    fun repeatedRemoveSucceeds() {
+        val originalValue = getValue()
+        val testOne = openOne()
+
+        addLoader(testOne)
+
+        assertNotEquals(originalValue, getValue())
+
+        removeLoader(testOne)
+
+        assertEquals(originalValue, getValue())
+
+        removeLoader(testOne)
+
+        assertEquals(originalValue, getValue())
+    }
+
+    @Test
+    fun addToFront() {
+        val testOne = openOne()
+        val testTwo = openTwo()
+
+        addLoader(testOne)
+
+        assertEquals(valueOne, getValue())
+
+        addLoader(testTwo, 0)
+
+        assertEquals(valueOne, getValue())
+
+        // Remove top loader, so previously added to front should now resolve
+        removeLoader(testOne)
+        assertEquals(valueTwo, getValue())
+    }
+
+    @Test
+    fun addToEnd() {
+        val testOne = openOne()
+        val testTwo = openTwo()
+
+        addLoader(testOne)
+
+        assertEquals(valueOne, getValue())
+
+        addLoader(testTwo, 1)
+
+        assertEquals(valueTwo, getValue())
+    }
+
+    @Test(expected = IndexOutOfBoundsException::class)
+    fun addPastEnd() {
+        val testOne = openOne()
+        val testTwo = openTwo()
+
+        addLoader(testOne)
+
+        assertEquals(valueOne, getValue())
+
+        addLoader(testTwo, 2)
+    }
+
+    @Test(expected = IndexOutOfBoundsException::class)
+    fun addBeforeFront() {
+        val testOne = openOne()
+        val testTwo = openTwo()
+
+        addLoader(testOne)
+
+        assertEquals(valueOne, getValue())
+
+        addLoader(testTwo, -1)
+    }
+
+    @Test
+    fun reorder() {
+        val originalValue = getValue()
+        val testOne = openOne()
+        val testTwo = openTwo()
+
+        addLoader(testOne, testTwo)
+
+        assertEquals(valueTwo, getValue())
+
+        removeLoader(testOne)
+
+        assertEquals(valueTwo, getValue())
+
+        addLoader(testOne)
+
+        assertEquals(valueOne, getValue())
+
+        removeLoader(testTwo)
+
+        assertEquals(valueOne, getValue())
+
+        removeLoader(testOne)
+
+        assertEquals(originalValue, getValue())
+    }
+
+    data class Parameter(
+        val getValue: ResourceLoaderValuesTest.() -> Any,
+        val loaderOne: String,
+        val valueOne: ResourceLoaderValuesTest.() -> Any,
+        val loaderTwo: String,
+        val valueTwo: ResourceLoaderValuesTest.() -> Any,
+        val dataTypes: List<DataType>
+    ) {
+        override fun toString(): String {
+            val prefix = loaderOne.commonPrefixWith(loaderTwo)
+            return "$prefix${loaderOne.removePrefix(prefix)}|${loaderTwo.removePrefix(prefix)}"
+        }
+    }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
new file mode 100644
index 0000000..df2d09a
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.content.res.loader.test
+
+import android.content.Context
+import android.content.res.AssetFileDescriptor
+import android.content.res.Resources
+import android.os.ParcelFileDescriptor
+import android.util.TypedValue
+import org.mockito.Answers
+import org.mockito.stubbing.Answer
+import org.xmlpull.v1.XmlPullParser
+import java.io.File
+
+// Enforce use of [android.util.Pair] instead of Kotlin's so it matches the ResourceLoader APIs
+typealias Pair<F, S> = android.util.Pair<F, S>
+infix fun <A, B> A.to(that: B): Pair<A, B> = Pair.create(this, that)!!
+
+object Utils {
+    val ANSWER_THROWS = Answer<Any> {
+        when (val name = it.method.name) {
+            "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it)
+            else -> throw UnsupportedOperationException("$name with " +
+                    "${it.arguments?.joinToString()} should not be called")
+        }
+    }
+}
+
+fun Int.dpToPx(resources: Resources) = TypedValue.applyDimension(
+        TypedValue.COMPLEX_UNIT_DIP,
+        this.toFloat(),
+        resources.displayMetrics
+).toInt()
+
+fun AssetFileDescriptor.readText() = createInputStream().reader().readText()
+
+fun rawFile(fileName: String) = R.raw::class.java.getDeclaredField(fileName).getInt(null)
+
+fun XmlPullParser.advanceToRoot() = apply {
+    while (next() != XmlPullParser.START_TAG) {
+        // Empty
+    }
+}
+
+fun Context.copiedRawFile(fileName: String): ParcelFileDescriptor {
+    return resources.openRawResourceFd(rawFile(fileName)).use { asset ->
+        // AssetManager doesn't expose a direct file descriptor to the asset, so copy it to
+        // an individual file so one can be created manually.
+        val copiedFile = File(filesDir, fileName)
+        asset.createInputStream().use { input ->
+            copiedFile.outputStream().use { output ->
+                input.copyTo(output)
+            }
+        }
+
+        ParcelFileDescriptor.open(copiedFile, ParcelFileDescriptor.MODE_READ_WRITE)
+    }
+}
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/core/tests/coretests/src/android/util/TimestampedValueTest.java b/core/tests/coretests/src/android/util/TimestampedValueTest.java
index 6e3ab79..6fc2400 100644
--- a/core/tests/coretests/src/android/util/TimestampedValueTest.java
+++ b/core/tests/coretests/src/android/util/TimestampedValueTest.java
@@ -55,12 +55,12 @@
         TimestampedValue<String> stringValue = new TimestampedValue<>(1000, "Hello");
         Parcel parcel = Parcel.obtain();
         try {
-            TimestampedValue.writeToParcel(parcel, stringValue);
+            parcel.writeParcelable(stringValue, 0);
 
             parcel.setDataPosition(0);
 
             TimestampedValue<String> stringValueCopy =
-                    TimestampedValue.readFromParcel(parcel, null /* classLoader */, String.class);
+                    parcel.readParcelable(null /* classLoader */);
             assertEquals(stringValue, stringValueCopy);
         } finally {
             parcel.recycle();
@@ -72,12 +72,12 @@
         TimestampedValue<String> stringValue = new TimestampedValue<>(1000, "Hello");
         Parcel parcel = Parcel.obtain();
         try {
-            TimestampedValue.writeToParcel(parcel, stringValue);
+            parcel.writeParcelable(stringValue, 0);
 
             parcel.setDataPosition(0);
 
-            TimestampedValue<Object> stringValueCopy =
-                    TimestampedValue.readFromParcel(parcel, null /* classLoader */, Object.class);
+            TimestampedValue<String> stringValueCopy =
+                    parcel.readParcelable(null /* classLoader */);
             assertEquals(stringValue, stringValueCopy);
         } finally {
             parcel.recycle();
@@ -85,15 +85,15 @@
     }
 
     @Test
-    public void testParceling_valueClassIncompatible() {
-        TimestampedValue<String> stringValue = new TimestampedValue<>(1000, "Hello");
+    public void testParceling_valueClassNotParcelable() {
+        // This class is not one supported by Parcel.writeValue().
+        class NotParcelable {}
+
+        TimestampedValue<NotParcelable> notParcelableValue =
+                new TimestampedValue<>(1000, new NotParcelable());
         Parcel parcel = Parcel.obtain();
         try {
-            TimestampedValue.writeToParcel(parcel, stringValue);
-
-            parcel.setDataPosition(0);
-
-            TimestampedValue.readFromParcel(parcel, null /* classLoader */, Double.class);
+            parcel.writeParcelable(notParcelableValue, 0);
             fail();
         } catch (RuntimeException expected) {
         } finally {
@@ -106,12 +106,11 @@
         TimestampedValue<String> nullValue = new TimestampedValue<>(1000, null);
         Parcel parcel = Parcel.obtain();
         try {
-            TimestampedValue.writeToParcel(parcel, nullValue);
+            parcel.writeParcelable(nullValue, 0);
 
             parcel.setDataPosition(0);
 
-            TimestampedValue<Object> nullValueCopy =
-                    TimestampedValue.readFromParcel(parcel, null /* classLoader */, String.class);
+            TimestampedValue<String> nullValueCopy = parcel.readParcelable(null /* classLoader */);
             assertEquals(nullValue, nullValueCopy);
         } finally {
             parcel.recycle();
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index abee1da2..7b405434 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -345,9 +345,9 @@
         accessibilityShortcutController.performAccessibilityShortcut();
         accessibilityShortcutController.performAccessibilityShortcut();
         verify(mToast).show();
-        assertEquals(WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+        assertEquals(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
                 mLayoutParams.privateFlags
-                        & WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS);
+                        & WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
         verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut();
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
new file mode 100644
index 0000000..5eec91c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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;
+
+
+import android.test.AndroidTestCase;
+
+import java.util.Arrays;
+
+
+public class LockscreenCredentialTest extends AndroidTestCase {
+
+    public void testEmptyCredential() {
+        LockscreenCredential empty = LockscreenCredential.createNone();
+
+        assertTrue(empty.isNone());
+        assertEquals(0, empty.size());
+        assertNotNull(empty.getCredential());
+
+        assertFalse(empty.isPassword());
+        assertFalse(empty.isPattern());
+    }
+
+    public void testPasswordCredential() {
+        LockscreenCredential password = LockscreenCredential.createPassword("password");
+
+        assertTrue(password.isPassword());
+        assertEquals(8, password.size());
+        assertTrue(Arrays.equals("password".getBytes(), password.getCredential()));
+
+        assertFalse(password.isNone());
+        assertFalse(password.isPattern());
+    }
+
+    public void testPatternCredential() {
+        LockscreenCredential pattern = LockscreenCredential.createPattern(Arrays.asList(
+                LockPatternView.Cell.of(0, 0),
+                LockPatternView.Cell.of(0, 1),
+                LockPatternView.Cell.of(0, 2),
+                LockPatternView.Cell.of(1, 2),
+                LockPatternView.Cell.of(2, 2)
+                ));
+
+        assertTrue(pattern.isPattern());
+        assertEquals(5, pattern.size());
+        assertTrue(Arrays.equals("12369".getBytes(), pattern.getCredential()));
+
+        assertFalse(pattern.isNone());
+        assertFalse(pattern.isPassword());
+    }
+
+    public void testPasswordOrNoneCredential() {
+        assertEquals(LockscreenCredential.createNone(),
+                LockscreenCredential.createPasswordOrNone(null));
+        assertEquals(LockscreenCredential.createNone(),
+                LockscreenCredential.createPasswordOrNone(""));
+        assertEquals(LockscreenCredential.createPassword("abcd"),
+                LockscreenCredential.createPasswordOrNone("abcd"));
+    }
+
+    public void testSanitize() {
+        LockscreenCredential password = LockscreenCredential.createPassword("password");
+        password.zeroize();
+
+        try {
+            password.isNone();
+            fail("Sanitized credential still accessible");
+        } catch (IllegalStateException expected) { }
+
+        try {
+            password.isPattern();
+            fail("Sanitized credential still accessible");
+        } catch (IllegalStateException expected) { }
+        try {
+            password.isPassword();
+            fail("Sanitized credential still accessible");
+        } catch (IllegalStateException expected) { }
+        try {
+            password.size();
+            fail("Sanitized credential still accessible");
+        } catch (IllegalStateException expected) { }
+        try {
+            password.getCredential();
+            fail("Sanitized credential still accessible");
+        } catch (IllegalStateException expected) { }
+    }
+
+    public void testEquals() {
+        assertEquals(LockscreenCredential.createNone(), LockscreenCredential.createNone());
+        assertEquals(LockscreenCredential.createPassword("1234"),
+                LockscreenCredential.createPassword("1234"));
+        assertEquals(LockscreenCredential.createPin("4321"),
+                LockscreenCredential.createPin("4321"));
+        assertEquals(createPattern("1234"), createPattern("1234"));
+
+        assertNotSame(LockscreenCredential.createPassword("1234"),
+                LockscreenCredential.createNone());
+        assertNotSame(LockscreenCredential.createPassword("1234"),
+                LockscreenCredential.createPassword("4321"));
+        assertNotSame(LockscreenCredential.createPassword("1234"),
+                createPattern("1234"));
+        assertNotSame(LockscreenCredential.createPassword("1234"),
+                LockscreenCredential.createPin("1234"));
+
+        assertNotSame(LockscreenCredential.createPin("1111"),
+                LockscreenCredential.createNone());
+        assertNotSame(LockscreenCredential.createPin("1111"),
+                LockscreenCredential.createPin("2222"));
+        assertNotSame(LockscreenCredential.createPin("1111"),
+                createPattern("1111"));
+        assertNotSame(LockscreenCredential.createPin("1111"),
+                LockscreenCredential.createPassword("1111"));
+
+        assertNotSame(createPattern("5678"),
+                LockscreenCredential.createNone());
+        assertNotSame(createPattern("5678"),
+                createPattern("1234"));
+        assertNotSame(createPattern("5678"),
+                LockscreenCredential.createPassword("5678"));
+        assertNotSame(createPattern("5678"),
+                LockscreenCredential.createPin("5678"));
+    }
+
+    public void testDuplicate() {
+        LockscreenCredential credential;
+
+        credential = LockscreenCredential.createNone();
+        assertEquals(credential, credential.duplicate());
+        credential = LockscreenCredential.createPassword("abcd");
+        assertEquals(credential, credential.duplicate());
+        credential = LockscreenCredential.createPin("1234");
+        assertEquals(credential, credential.duplicate());
+        credential = createPattern("5678");
+        assertEquals(credential, credential.duplicate());
+    }
+
+    private LockscreenCredential createPattern(String patternString) {
+        return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+                patternString.getBytes()));
+    }
+}
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index d36a826..61281ee 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -16,17 +16,30 @@
   -->
 <permissions>
     <privapp-permissions package="com.google.android.car.kitchensink">
+        <permission name="android.permission.ACCESS_NETWORK_STATE"/>
+        <permission name="android.permission.ACCESS_WIFI_STATE"/>
+        <permission name="android.permission.ACTIVITY_EMBEDDING"/>
+        <permission name="android.permission.INJECT_EVENTS"/>
+        <!-- use for CarServiceUnitTest and CarServiceTest -->
+        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+        <!-- use for CarServiceUnitTest -->
+        <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
         <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
         <permission name="android.permission.LOCATION_HARDWARE"/>
         <permission name="android.permission.MANAGE_USB"/>
         <permission name="android.permission.MANAGE_USERS"/>
+        <!-- use for CarServiceTest -->
+        <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
         <permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
         <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.PROVIDE_TRUST_AGENT"/>
+        <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
         <permission name="android.permission.REAL_GET_TASKS"/>
         <permission name="android.permission.READ_LOGS"/>
         <permission name="android.permission.REBOOT"/>
+        <!-- use for CarServiceTest -->
+        <permission name="android.permission.SET_ACTIVITY_WATCHER"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/framework-sysconfig.xml b/data/etc/framework-sysconfig.xml
index 987c3b4..7296cfd 100644
--- a/data/etc/framework-sysconfig.xml
+++ b/data/etc/framework-sysconfig.xml
@@ -29,6 +29,8 @@
          'service' attribute here is a flattened ComponentName string. -->
     <backup-transport-whitelisted-service
         service="com.android.localtransport/.LocalTransportService" />
+    <backup-transport-whitelisted-service
+        service="com.android.encryptedlocaltransport/.EncryptedLocalTransportService" />
 
     <!-- Whitelist Shell to use the bugreport API -->
     <bugreport-whitelisted package="com.android.shell" />
diff --git a/data/etc/hiddenapi-package-whitelist.xml b/data/etc/hiddenapi-package-whitelist.xml
index f1ba3f6..07a5617 100644
--- a/data/etc/hiddenapi-package-whitelist.xml
+++ b/data/etc/hiddenapi-package-whitelist.xml
@@ -56,7 +56,7 @@
   <!-- TODO (b/141954427): Remove networkstack -->
   <hidden-api-whitelisted-app package="com.android.networkstack" />
   <!-- TODO (b/141954427): Remove wifistack -->
-  <hidden-api-whitelisted-app package="com.android.server.wifistack" />
+  <hidden-api-whitelisted-app package="com.android.wifi" />
   <hidden-api-whitelisted-app package="com.android.smspush" />
   <hidden-api-whitelisted-app package="com.android.spare_parts" />
   <hidden-api-whitelisted-app package="com.android.statementservice" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 51136b9..4b4e416 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -121,6 +121,7 @@
         <permission name="android.permission.APPROVE_INCIDENT_REPORTS"/>
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
+        <permission name="android.permission.PACKAGE_USAGE_STATS" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.phone">
@@ -354,11 +355,12 @@
         <permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
     </privapp-permissions>
 
-    <privapp-permissions package="com.android.server.wifistack">
+    <privapp-permissions package="com.android.wifi">
         <permission name="android.permission.CHANGE_CONFIGURATION"/>
         <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
         <permission name="android.permission.DUMP"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+        <permission name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS"/>
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
index 454dceb..4226e08 100644
--- a/data/fonts/Android.mk
+++ b/data/fonts/Android.mk
@@ -83,8 +83,8 @@
 
 ################################
 # Copies the font configuration file into system/etc for the product as fonts.xml.
-# In the case where $(ADDITIONAL_FONTS_FILE) is defined, the content of $(ADDITIONAL_FONTS_FILE)
-# is added to the $(AOSP_FONTS_FILE).
+# Additional fonts should be installed to /product/fonts/ alongside a corresponding
+# fonts_customiztion.xml in /product/etc/
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := fonts.xml
diff --git a/graphics/java/android/graphics/pdf/TEST_MAPPING b/graphics/java/android/graphics/pdf/TEST_MAPPING
new file mode 100644
index 0000000..d763598
--- /dev/null
+++ b/graphics/java/android/graphics/pdf/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsPdfTestCases"
+    }
+  ]
+}
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index bf23634..254456c 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -350,7 +350,7 @@
      * access manually.
      */
     public static final String KEY_ALIAS_SELECTION_DENIED =
-            "alias-selection-denied-ef829e15-210a-409d-96c9-ee684fc41972";
+            "android:alias-selection-denied";
 
     /**
      * Returns an {@code Intent} that can be used for credential
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index eeaefc5..a34a6c0 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -166,7 +166,10 @@
             static_libs: common_test_libs + ["liblog", "libz"],
         },
     },
-    data: ["tests/data/**/*.apk"],
+    data: [
+      "tests/data/**/*.apk",
+      "tests/data/**/*.arsc",
+    ],
     test_suites: ["device-tests"],
 }
 
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index cf2ef30..b309621 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -42,12 +42,16 @@
 
 ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle,
                      const std::string& path,
-                     time_t last_mod_time)
-    : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time) {
+                     time_t last_mod_time,
+                     bool for_loader)
+    : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time),
+      for_loader(for_loader) {
 }
 
-std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) {
-  return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/);
+std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system,
+                                                 bool for_loader) {
+  return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/,
+                  for_loader);
 }
 
 std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path,
@@ -76,9 +80,21 @@
 
 std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd,
                                                        const std::string& friendly_name,
-                                                       bool system, bool force_shared_lib) {
+                                                       bool system, bool force_shared_lib,
+                                                       bool for_loader) {
   return LoadImpl(std::move(fd), friendly_name, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/,
-                  system, force_shared_lib);
+                  system, force_shared_lib, for_loader);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(const std::string& path,
+                                                     bool for_loader) {
+  return LoadArscImpl({} /*fd*/, path, for_loader);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(unique_fd fd,
+                                                     const std::string& friendly_name,
+                                                     bool for_loader) {
+  return LoadArscImpl(std::move(fd), friendly_name, for_loader);
 }
 
 std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
@@ -104,7 +120,8 @@
 
 std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
     unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset,
-    std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) {
+    std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library,
+    bool for_loader) {
   ::ZipArchiveHandle unmanaged_handle;
   int32_t result;
   if (fd >= 0) {
@@ -123,7 +140,8 @@
   time_t last_mod_time = getFileModDate(path.c_str());
 
   // Wrap the handle in a unique_ptr so it gets automatically closed.
-  std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time));
+  std::unique_ptr<ApkAssets>
+      loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time, for_loader));
 
   // Find the resource table.
   ::ZipEntry entry;
@@ -152,7 +170,7 @@
       reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
       loaded_apk->resources_asset_->getLength());
   loaded_apk->loaded_arsc_ =
-      LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library);
+      LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library, for_loader);
   if (loaded_apk->loaded_arsc_ == nullptr) {
     LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'.";
     return {};
@@ -162,8 +180,53 @@
   return std::move(loaded_apk);
 }
 
+std::unique_ptr<const ApkAssets> ApkAssets::LoadArscImpl(unique_fd fd,
+                                                         const std::string& path,
+                                                         bool for_loader) {
+  std::unique_ptr<Asset> resources_asset;
+
+  if (fd >= 0) {
+    resources_asset = std::unique_ptr<Asset>(Asset::createFromFd(fd.release(), nullptr,
+        Asset::AccessMode::ACCESS_BUFFER));
+  } else {
+    resources_asset = CreateAssetFromFile(path);
+  }
+
+  if (resources_asset == nullptr) {
+    LOG(ERROR) << "Failed to open ARSC '" << path;
+    return {};
+  }
+
+  time_t last_mod_time = getFileModDate(path.c_str());
+
+  std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(nullptr, path, last_mod_time, for_loader));
+  loaded_apk->resources_asset_ = std::move(resources_asset);
+
+  const StringPiece data(
+      reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
+      loaded_apk->resources_asset_->getLength());
+  loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, nullptr, false, false, for_loader);
+  if (loaded_apk->loaded_arsc_ == nullptr) {
+    LOG(ERROR) << "Failed to load '" << kResourcesArsc << path;
+    return {};
+  }
+
+  // Need to force a move for mingw32.
+  return std::move(loaded_apk);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty(bool for_loader) {
+  std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(nullptr, "", -1, for_loader));
+  loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
+  // Need to force a move for mingw32.
+  return std::move(loaded_apk);
+}
+
 std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const {
-  CHECK(zip_handle_ != nullptr);
+  // If this is a resource loader from an .arsc, there will be no zip handle
+  if (zip_handle_ == nullptr) {
+    return {};
+  }
 
   ::ZipEntry entry;
   int32_t result = ::FindEntry(zip_handle_.get(), path, &entry);
@@ -205,7 +268,10 @@
 
 bool ApkAssets::ForEachFile(const std::string& root_path,
                             const std::function<void(const StringPiece&, FileType)>& f) const {
-  CHECK(zip_handle_ != nullptr);
+  // If this is a resource loader from an .arsc, there will be no zip handle
+  if (zip_handle_ == nullptr) {
+    return false;
+  }
 
   std::string root_path_full = root_path;
   if (root_path_full.back() != '/') {
@@ -252,6 +318,11 @@
 }
 
 bool ApkAssets::IsUpToDate() const {
+  // Loaders are invalidated by the app, not the system, so assume up to date
+  if (for_loader) {
+    return true;
+  }
+
   return last_mod_time_ == getFileModDate(path_.c_str());
 }
 
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
index 92125c9..c132f34 100644
--- a/libs/androidfw/Asset.cpp
+++ b/libs/androidfw/Asset.cpp
@@ -133,14 +133,24 @@
  */
 /*static*/ Asset* Asset::createFromFile(const char* fileName, AccessMode mode)
 {
+    return createFromFd(open(fileName, O_RDONLY | O_BINARY), fileName, mode);
+}
+
+/*
+ * Create a new Asset from a file on disk.  There is a fair chance that
+ * the file doesn't actually exist.
+ *
+ * We can use "mode" to decide how we want to go about it.
+ */
+/*static*/ Asset* Asset::createFromFd(const int fd, const char* fileName, AccessMode mode)
+{
+    if (fd < 0) {
+        return NULL;
+    }
+
     _FileAsset* pAsset;
     status_t result;
     off64_t length;
-    int fd;
-
-    fd = open(fileName, O_RDONLY | O_BINARY);
-    if (fd < 0)
-        return NULL;
 
     /*
      * Under Linux, the lseek fails if we actually opened a directory.  To
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index eec49df..e914f37 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -493,8 +493,12 @@
 
     type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx);
 
-    // If the package is an overlay, then even configurations that are the same MUST be chosen.
+
+    // If the package is an overlay or custom loader,
+    // then even configurations that are the same MUST be chosen.
     const bool package_is_overlay = loaded_package->IsOverlay();
+    const bool package_is_loader = loaded_package->IsCustomLoader();
+    const bool should_overlay = package_is_overlay || package_is_loader;
 
     if (use_fast_path) {
       const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
@@ -508,10 +512,28 @@
         if (best_config == nullptr) {
           resolution_type = Resolution::Step::Type::INITIAL;
         } else if (this_config.isBetterThan(*best_config, desired_config)) {
-          resolution_type = Resolution::Step::Type::BETTER_MATCH;
-        } else if (package_is_overlay && this_config.compare(*best_config) == 0) {
-          resolution_type = Resolution::Step::Type::OVERLAID;
+          if (package_is_loader) {
+            resolution_type = Resolution::Step::Type::BETTER_MATCH_LOADER;
+          } else {
+            resolution_type = Resolution::Step::Type::BETTER_MATCH;
+          }
+        } else if (should_overlay && this_config.compare(*best_config) == 0) {
+          if (package_is_loader) {
+            resolution_type = Resolution::Step::Type::OVERLAID_LOADER;
+          } else if (package_is_overlay) {
+            resolution_type = Resolution::Step::Type::OVERLAID;
+          }
         } else {
+          if (resource_resolution_logging_enabled_) {
+            if (package_is_loader) {
+              resolution_type = Resolution::Step::Type::SKIPPED_LOADER;
+            } else {
+              resolution_type = Resolution::Step::Type::SKIPPED;
+            }
+            resolution_steps.push_back(Resolution::Step{resolution_type,
+                                                        this_config.toString(),
+                                                        &loaded_package->GetPackageName()});
+          }
           continue;
         }
 
@@ -520,6 +542,16 @@
         const ResTable_type* type = filtered_group.types[i];
         const uint32_t offset = LoadedPackage::GetEntryOffset(type, local_entry_idx);
         if (offset == ResTable_type::NO_ENTRY) {
+          if (resource_resolution_logging_enabled_) {
+            if (package_is_loader) {
+              resolution_type = Resolution::Step::Type::NO_ENTRY_LOADER;
+            } else {
+              resolution_type = Resolution::Step::Type::NO_ENTRY;
+            }
+            resolution_steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY,
+                                                        this_config.toString(),
+                                                        &loaded_package->GetPackageName()});
+          }
           continue;
         }
 
@@ -554,9 +586,17 @@
           if (best_config == nullptr) {
             resolution_type = Resolution::Step::Type::INITIAL;
           } else if (this_config.isBetterThan(*best_config, desired_config)) {
-            resolution_type = Resolution::Step::Type::BETTER_MATCH;
-          } else if (package_is_overlay && this_config.compare(*best_config) == 0) {
-            resolution_type = Resolution::Step::Type::OVERLAID;
+            if (package_is_loader) {
+              resolution_type = Resolution::Step::Type::BETTER_MATCH_LOADER;
+            } else {
+              resolution_type = Resolution::Step::Type::BETTER_MATCH;
+            }
+          } else if (should_overlay && this_config.compare(*best_config) == 0) {
+            if (package_is_overlay) {
+              resolution_type = Resolution::Step::Type::OVERLAID;
+            } else if (package_is_loader) {
+              resolution_type = Resolution::Step::Type::OVERLAID_LOADER;
+            }
           } else {
             continue;
           }
@@ -678,9 +718,27 @@
       case Resolution::Step::Type::BETTER_MATCH:
         prefix = "Found better";
         break;
+      case Resolution::Step::Type::BETTER_MATCH_LOADER:
+        prefix = "Found better in loader";
+        break;
       case Resolution::Step::Type::OVERLAID:
         prefix = "Overlaid";
         break;
+      case Resolution::Step::Type::OVERLAID_LOADER:
+        prefix = "Overlaid by loader";
+        break;
+      case Resolution::Step::Type::SKIPPED:
+        prefix = "Skipped";
+        break;
+      case Resolution::Step::Type::SKIPPED_LOADER:
+        prefix = "Skipped loader";
+        break;
+      case Resolution::Step::Type::NO_ENTRY:
+        prefix = "No entry";
+        break;
+      case Resolution::Step::Type::NO_ENTRY_LOADER:
+        prefix = "No entry for loader";
+        break;
     }
 
     if (!prefix.empty()) {
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 72873ab..882dc0d 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -401,7 +401,9 @@
 
 std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
                                                          const LoadedIdmap* loaded_idmap,
-                                                         bool system, bool load_as_shared_library) {
+                                                         bool system,
+                                                         bool load_as_shared_library,
+                                                         bool for_loader) {
   ATRACE_NAME("LoadedPackage::Load");
   std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
 
@@ -430,6 +432,10 @@
     loaded_package->overlay_ = true;
   }
 
+  if (for_loader) {
+    loaded_package->custom_loader_ = true;
+  }
+
   if (header->header.headerSize >= sizeof(ResTable_package)) {
     uint32_t type_id_offset = dtohl(header->typeIdOffset);
     if (type_id_offset > std::numeric_limits<uint8_t>::max()) {
@@ -696,7 +702,7 @@
 }
 
 bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
-                           bool load_as_shared_library) {
+                           bool load_as_shared_library, bool for_loader) {
   const ResTable_header* header = chunk.header<ResTable_header>();
   if (header == nullptr) {
     LOG(ERROR) << "RES_TABLE_TYPE too small.";
@@ -735,7 +741,11 @@
         packages_seen++;
 
         std::unique_ptr<const LoadedPackage> loaded_package =
-            LoadedPackage::Load(child_chunk, loaded_idmap, system_, load_as_shared_library);
+            LoadedPackage::Load(child_chunk,
+                                loaded_idmap,
+                                system_,
+                                load_as_shared_library,
+                                for_loader);
         if (!loaded_package) {
           return false;
         }
@@ -758,9 +768,11 @@
 }
 
 std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data,
-                                                   const LoadedIdmap* loaded_idmap, bool system,
-                                                   bool load_as_shared_library) {
-  ATRACE_NAME("LoadedArsc::LoadTable");
+                                                   const LoadedIdmap* loaded_idmap,
+                                                   bool system,
+                                                   bool load_as_shared_library,
+                                                   bool for_loader) {
+  ATRACE_NAME("LoadedArsc::Load");
 
   // Not using make_unique because the constructor is private.
   std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
@@ -771,7 +783,10 @@
     const Chunk chunk = iter.Next();
     switch (chunk.type()) {
       case RES_TABLE_TYPE:
-        if (!loaded_arsc->LoadTable(chunk, loaded_idmap, load_as_shared_library)) {
+        if (!loaded_arsc->LoadTable(chunk,
+                                    loaded_idmap,
+                                    load_as_shared_library,
+                                    for_loader)) {
           return {};
         }
         break;
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 49fc82b..625b6820 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -40,7 +40,8 @@
   // Creates an ApkAssets.
   // If `system` is true, the package is marked as a system package, and allows some functions to
   // filter out this package when computing what configurations/resources are available.
-  static std::unique_ptr<const ApkAssets> Load(const std::string& path, bool system = false);
+  static std::unique_ptr<const ApkAssets> Load(const std::string& path, bool system = false,
+                                               bool for_loader = false);
 
   // Creates an ApkAssets, but forces any package with ID 0x7f to be loaded as a shared library.
   // If `system` is true, the package is marked as a system package, and allows some functions to
@@ -63,7 +64,21 @@
   // If `force_shared_lib` is true, any package with ID 0x7f is loaded as a shared library.
   static std::unique_ptr<const ApkAssets> LoadFromFd(base::unique_fd fd,
                                                      const std::string& friendly_name, bool system,
-                                                     bool force_shared_lib);
+                                                     bool force_shared_lib,
+                                                     bool for_loader = false);
+
+  // Creates an empty wrapper ApkAssets from the given path which points to an .arsc.
+  static std::unique_ptr<const ApkAssets> LoadArsc(const std::string& path,
+                                                   bool for_loader = false);
+
+  // Creates an empty wrapper ApkAssets from the given file descriptor which points to an .arsc,
+  // Takes ownership of the file descriptor.
+  static std::unique_ptr<const ApkAssets> LoadArsc(base::unique_fd fd,
+                                                   const std::string& friendly_name,
+                                                   bool resource_loader = false);
+
+  // Creates a totally empty ApkAssets with no resources table and no file entries.
+  static std::unique_ptr<const ApkAssets> LoadEmpty(bool resource_loader = false);
 
   std::unique_ptr<Asset> Open(const std::string& path,
                               Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
@@ -86,24 +101,33 @@
 
   bool IsUpToDate() const;
 
+  // Creates an Asset from any file on the file system.
+  static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
+
  private:
   DISALLOW_COPY_AND_ASSIGN(ApkAssets);
 
   static std::unique_ptr<const ApkAssets> LoadImpl(base::unique_fd fd, const std::string& path,
                                                    std::unique_ptr<Asset> idmap_asset,
                                                    std::unique_ptr<const LoadedIdmap> loaded_idmap,
-                                                   bool system, bool load_as_shared_library);
+                                                   bool system, bool load_as_shared_library,
+                                                   bool resource_loader = false);
 
-  // Creates an Asset from any file on the file system.
-  static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
+  static std::unique_ptr<const ApkAssets> LoadArscImpl(base::unique_fd fd,
+                                                       const std::string& path,
+                                                       bool resource_loader = false);
 
-  ApkAssets(ZipArchiveHandle unmanaged_handle, const std::string& path, time_t last_mod_time);
+  ApkAssets(ZipArchiveHandle unmanaged_handle,
+            const std::string& path,
+            time_t last_mod_time,
+            bool for_loader = false);
 
-  using ZipArchivePtr = std::unique_ptr<ZipArchive, void(*)(ZipArchiveHandle)>;
+  using ZipArchivePtr = std::unique_ptr<ZipArchive, void (*)(ZipArchiveHandle)>;
 
   ZipArchivePtr zip_handle_;
   const std::string path_;
   time_t last_mod_time_;
+  bool for_loader;
   std::unique_ptr<Asset> resources_asset_;
   std::unique_ptr<Asset> idmap_asset_;
   std::unique_ptr<const LoadedArsc> loaded_arsc_;
diff --git a/libs/androidfw/include/androidfw/Asset.h b/libs/androidfw/include/androidfw/Asset.h
index 9d12a35..053dbb7 100644
--- a/libs/androidfw/include/androidfw/Asset.h
+++ b/libs/androidfw/include/androidfw/Asset.h
@@ -121,6 +121,11 @@
      */
     const char* getAssetSource(void) const { return mAssetSource.string(); }
 
+    /*
+     * Create the asset from a file descriptor.
+     */
+    static Asset* createFromFd(const int fd, const char* fileName, AccessMode mode);
+
 protected:
     /*
      * Adds this Asset to the global Asset list for debugging and
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index de46081..c7348b1 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -382,7 +382,13 @@
       enum class Type {
         INITIAL,
         BETTER_MATCH,
-        OVERLAID
+        BETTER_MATCH_LOADER,
+        OVERLAID,
+        OVERLAID_LOADER,
+        SKIPPED,
+        SKIPPED_LOADER,
+        NO_ENTRY,
+        NO_ENTRY_LOADER,
       };
 
       // Marks what kind of override this step was.
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 950f541..1a56876 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -137,7 +137,8 @@
 
   static std::unique_ptr<const LoadedPackage> Load(const Chunk& chunk,
                                                    const LoadedIdmap* loaded_idmap, bool system,
-                                                   bool load_as_shared_library);
+                                                   bool load_as_shared_library,
+                                                   bool load_as_custom_loader);
 
   ~LoadedPackage();
 
@@ -187,6 +188,11 @@
     return overlay_;
   }
 
+  // Returns true if this package is a custom loader and should behave like an overlay
+  inline bool IsCustomLoader() const {
+    return custom_loader_;
+  }
+
   // Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
   // package could have been assigned a different package ID than what this LoadedPackage was
   // compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime.
@@ -260,6 +266,7 @@
   bool dynamic_ = false;
   bool system_ = false;
   bool overlay_ = false;
+  bool custom_loader_ = false;
   bool defines_overlayable_ = false;
 
   ByteBucketArray<TypeSpecPtr> type_specs_;
@@ -282,7 +289,8 @@
   static std::unique_ptr<const LoadedArsc> Load(const StringPiece& data,
                                                 const LoadedIdmap* loaded_idmap = nullptr,
                                                 bool system = false,
-                                                bool load_as_shared_library = false);
+                                                bool load_as_shared_library = false,
+                                                bool for_loader = false);
 
   // Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
   static std::unique_ptr<const LoadedArsc> CreateEmpty();
@@ -311,7 +319,19 @@
   DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
 
   LoadedArsc() = default;
-  bool LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library);
+  bool LoadTable(
+      const Chunk& chunk,
+      const LoadedIdmap* loaded_idmap,
+      bool load_as_shared_library,
+      bool for_loader
+  );
+
+  static std::unique_ptr<const LoadedArsc> LoadData(std::unique_ptr<LoadedArsc>& loaded_arsc,
+                                                    const char* data,
+                                                    size_t length,
+                                                    const LoadedIdmap* loaded_idmap = nullptr,
+                                                    bool load_as_shared_library = false,
+                                                    bool for_loader = false);
 
   ResStringPool global_string_pool_;
   std::vector<std::unique_ptr<const LoadedPackage>> packages_;
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index d58e8d2..fd57a92 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -25,6 +25,7 @@
 #include "data/overlayable/R.h"
 #include "data/sparse/R.h"
 #include "data/styles/R.h"
+#include "data/system/R.h"
 
 namespace app = com::android::app;
 namespace basic = com::android::basic;
@@ -387,6 +388,39 @@
   ASSERT_EQ(map.at("OverlayableResources2"), "overlay://com.android.overlayable");
 }
 
+TEST(LoadedArscTest, LoadCustomLoader) {
+  std::string contents;
+
+  std::unique_ptr<Asset>
+      asset = ApkAssets::CreateAssetFromFile(GetTestDataPath() + "/loader/resources.arsc");
+
+  MockLoadedIdmap loaded_idmap;
+  const StringPiece data(
+      reinterpret_cast<const char*>(asset->getBuffer(true /*wordAligned*/)),
+      asset->getLength());
+
+  std::unique_ptr<const LoadedArsc> loaded_arsc =
+      LoadedArsc::Load(data, nullptr, false, false, true);
+  ASSERT_THAT(loaded_arsc, NotNull());
+
+  const LoadedPackage* package =
+      loaded_arsc->GetPackageById(get_package_id(android::R::string::cancel));
+  ASSERT_THAT(package, NotNull());
+  EXPECT_THAT(package->GetPackageName(), StrEq("android"));
+  EXPECT_THAT(package->GetPackageId(), Eq(0x01));
+
+  const uint8_t type_index = get_type_id(android::R::string::cancel) - 1;
+  const uint16_t entry_index = get_entry_id(android::R::string::cancel);
+
+  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+  ASSERT_THAT(type_spec, NotNull());
+  ASSERT_THAT(type_spec->type_count, Ge(1u));
+
+  const ResTable_type* type = type_spec->types[0];
+  ASSERT_THAT(type, NotNull());
+  ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull());
+}
+
 // structs with size fields (like Res_value, ResTable_entry) should be
 // backwards and forwards compatible (aka checking the size field against
 // sizeof(Res_value) might not be backwards compatible.
diff --git a/libs/androidfw/tests/data/loader/resources.arsc b/libs/androidfw/tests/data/loader/resources.arsc
new file mode 100644
index 0000000..2c881f2
--- /dev/null
+++ b/libs/androidfw/tests/data/loader/resources.arsc
Binary files differ
diff --git a/libs/androidfw/tests/data/system/R.h b/libs/androidfw/tests/data/system/R.h
index becb388..3741074 100644
--- a/libs/androidfw/tests/data/system/R.h
+++ b/libs/androidfw/tests/data/system/R.h
@@ -40,6 +40,12 @@
       number = 0x01030000,  // sv
     };
   };
+
+  struct string {
+    enum : uint32_t {
+      cancel = 0x01040000,
+    };
+  };
 };
 
 }  // namespace android
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index ae90f11..61b72cf 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -26,8 +26,10 @@
         // a problem
         "-Wno-free-nonheap-object",
 
-        // clang's warning is broken, see: https://llvm.org/bugs/show_bug.cgi?id=21629
-        "-Wno-missing-braces",
+        // Clang is producing non-determistic binary when the new pass manager is
+        // enabled. Disable the new PM as a temporary workaround.
+        // b/142372146
+        "-fno-experimental-new-pass-manager",
     ],
 
     include_dirs: [
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index 7d0b687..030a20f 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -27,7 +27,7 @@
 // Smaller than INT_MIN/INT_MAX because we offset these values
 // and thus don't want to be adding offsets to INT_MAX, that's bad
 #define DIRTY_MIN (-0x7ffffff - 1)
-#define DIRTY_MAX (0x7ffffff)
+#define DIRTY_MAX (0x8000000)
 
 namespace android {
 namespace uirenderer {
diff --git a/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java
index c41023e..944ebf9 100644
--- a/location/java/android/location/AbstractListenerManager.java
+++ b/location/java/android/location/AbstractListenerManager.java
@@ -42,8 +42,8 @@
         @Nullable private volatile T mListener;
 
         private Registration(Executor executor, T listener) {
-            Preconditions.checkArgument(listener != null);
-            Preconditions.checkArgument(executor != null);
+            Preconditions.checkArgument(listener != null, "invalid null listener/callback");
+            Preconditions.checkArgument(executor != null, "invalid null executor");
             mExecutor = executor;
             mListener = listener;
         }
@@ -83,16 +83,18 @@
         return addInternal(listener, executor);
     }
 
-    protected final boolean addInternal(Object listener, Handler handler) throws RemoteException {
+    protected final boolean addInternal(@NonNull Object listener, @NonNull Handler handler)
+            throws RemoteException {
         return addInternal(listener, new HandlerExecutor(handler));
     }
 
-    protected final boolean addInternal(Object listener, Executor executor) throws RemoteException {
+    protected final boolean addInternal(@NonNull Object listener, @NonNull Executor executor)
+            throws RemoteException {
+        Preconditions.checkArgument(listener != null, "invalid null listener/callback");
         return addInternal(listener, new Registration<>(executor, convertKey(listener)));
     }
 
     private boolean addInternal(Object key, Registration<T> registration) throws RemoteException {
-        Preconditions.checkNotNull(key);
         Preconditions.checkNotNull(registration);
 
         synchronized (mListeners) {
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index d06ba12..daa2e08 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -42,11 +42,11 @@
 interface ILocationManager
 {
     void requestLocationUpdates(in LocationRequest request, in ILocationListener listener,
-            in PendingIntent intent, String packageName);
+            in PendingIntent intent, String packageName, String listenerIdentifier);
     void removeUpdates(in ILocationListener listener, in PendingIntent intent, String packageName);
 
     void requestGeofence(in LocationRequest request, in Geofence geofence,
-            in PendingIntent intent, String packageName);
+            in PendingIntent intent, String packageName, String listenerIdentifier);
     void removeGeofence(in Geofence fence, in PendingIntent intent, String packageName);
 
     Location getLastLocation(in LocationRequest request, String packageName);
@@ -64,22 +64,23 @@
 
     boolean sendNiResponse(int notifId, int userResponse);
 
-    boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener, in String packageName);
+    boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener,
+             String packageName, String listenerIdentifier);
     void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections,
             in String packageName);
     long getGnssCapabilities(in String packageName);
     void removeGnssMeasurementsListener(in IGnssMeasurementsListener listener);
 
-    boolean addGnssNavigationMessageListener(
-            in IGnssNavigationMessageListener listener,
-            in String packageName);
+    boolean addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener,
+             String packageName, String listenerIdentifier);
     void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);
 
     int getGnssYearOfHardware();
     String getGnssHardwareModelName();
 
     int getGnssBatchSize(String packageName);
-    boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName);
+    boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName,
+             String listenerIdentifier);
     void removeGnssBatchingCallback();
     boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName);
     void flushGnssBatch(String packageName);
@@ -92,6 +93,7 @@
     String getBestProvider(in Criteria criteria, boolean enabledOnly);
     ProviderProperties getProviderProperties(String provider);
     boolean isProviderPackage(String packageName);
+    List<String> getProviderPackages(String provider);
 
     void setExtraLocationControllerPackage(String packageName);
     String getExtraLocationControllerPackage();
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 9c36d76..27274d1 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -16,6 +16,7 @@
 
 package android.location;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
@@ -79,6 +80,8 @@
      *
      * @hide
      */
+    @TestApi
+    @SystemApi
     public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
 
     /**
@@ -1208,14 +1211,16 @@
     }
 
     /**
-     * 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
      * @hide
      */
-    @UnsupportedAppUsage
-    public void setExtraLocation(String key, Location value) {
+    @TestApi
+    @SystemApi
+    public void setExtraLocation(@Nullable String key, @Nullable Location value) {
         if (mExtras == null) {
             mExtras = new Bundle();
         }
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 90e29df..0b3e1c3 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -51,6 +51,7 @@
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.util.Preconditions;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.RejectedExecutionException;
@@ -542,6 +543,23 @@
     }
 
     /**
+     * Create a string that allows an app to identify a listener
+     *
+     * @param listener The listener
+     *
+     * @return A identifying string
+     */
+    private static String getListenerIdentifier(@NonNull Object listener) {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(listener.getClass().getName());
+        sb.append('@');
+        sb.append(Integer.toHexString(System.identityHashCode(listener)));
+
+        return sb.toString();
+    }
+
+    /**
      * Register for a single location update using the named provider and
      * a callback.
      *
@@ -981,7 +999,7 @@
             boolean registered = false;
             try {
                 mService.requestLocationUpdates(locationRequest, transport, null,
-                        mContext.getPackageName());
+                        mContext.getPackageName(), getListenerIdentifier(listener));
                 registered = true;
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -1026,7 +1044,7 @@
 
         try {
             mService.requestLocationUpdates(locationRequest, null, pendingIntent,
-                    mContext.getPackageName());
+                    mContext.getPackageName(), getListenerIdentifier(pendingIntent));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1235,6 +1253,24 @@
     }
 
     /**
+     * Returns a list of packages associated with the given provider,
+     * and an empty list if no package is associated with the provider.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+    @Nullable
+    public List<String> getProviderPackages(@NonNull String provider) {
+        try {
+            return mService.getProviderPackages(provider);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return Collections.emptyList();
+        }
+    }
+
+    /**
      * Sends additional commands to a location provider. Can be used to support provider specific
      * extensions to the Location Manager API.
      *
@@ -1471,7 +1507,8 @@
         Geofence fence = Geofence.createCircle(latitude, longitude, radius);
         LocationRequest request = new LocationRequest().setExpireIn(expiration);
         try {
-            mService.requestGeofence(request, fence, intent, mContext.getPackageName());
+            mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
+                    getListenerIdentifier(intent));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1548,7 +1585,8 @@
         Preconditions.checkArgument(fence != null, "invalid null geofence");
 
         try {
-            mService.requestGeofence(request, fence, intent, mContext.getPackageName());
+            mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
+                    getListenerIdentifier(intent));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1762,6 +1800,7 @@
      * @param handler  a handler with a looper that the callback runs on
      * @return true if the listener was successfully added
      *
+     * @throws IllegalArgumentException if callback is null
      * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
      */
     @RequiresPermission(ACCESS_FINE_LOCATION)
@@ -1781,10 +1820,12 @@
     /**
      * Registers a GNSS status callback.
      *
-     * @param callback GNSS status callback object to register
      * @param executor the executor that the callback runs on
+     * @param callback GNSS status callback object to register
      * @return true if the listener was successfully added
      *
+     * @throws IllegalArgumentException if executor is null
+     * @throws IllegalArgumentException if callback is null
      * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
      */
     @RequiresPermission(ACCESS_FINE_LOCATION)
@@ -1853,6 +1894,8 @@
      * @param listener a {@link OnNmeaMessageListener} object to register
      * @param handler  a handler with the looper that the listener runs on.
      * @return true if the listener was successfully added
+     *
+     * @throws IllegalArgumentException if listener is null
      * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
      */
     @RequiresPermission(ACCESS_FINE_LOCATION)
@@ -1874,6 +1917,9 @@
      * @param listener a {@link OnNmeaMessageListener} object to register
      * @param executor the {@link Executor} that the listener runs on.
      * @return true if the listener was successfully added
+     *
+     * @throws IllegalArgumentException if executor is null
+     * @throws IllegalArgumentException if listener is null
      * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
      */
     @RequiresPermission(ACCESS_FINE_LOCATION)
@@ -1946,6 +1992,9 @@
      * @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
      * @param handler  the handler that the callback runs on.
      * @return {@code true} if the callback was added successfully, {@code false} otherwise.
+     *
+     * @throws IllegalArgumentException if callback is null
+     * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
      */
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public boolean registerGnssMeasurementsCallback(
@@ -1966,6 +2015,10 @@
      * @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
      * @param executor the executor that the callback runs on.
      * @return {@code true} if the callback was added successfully, {@code false} otherwise.
+     *
+     * @throws IllegalArgumentException if executor is null
+     * @throws IllegalArgumentException if callback is null
+     * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
      */
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public boolean registerGnssMeasurementsCallback(
@@ -1983,6 +2036,9 @@
      *
      * @param measurementCorrections a {@link GnssMeasurementCorrections} object with the GNSS
      *     measurement corrections to be injected into the GNSS chipset.
+     *
+     * @throws IllegalArgumentException if measurementCorrections is null
+     * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
      * @hide
      */
     @SystemApi
@@ -2057,6 +2113,9 @@
      * @param callback a {@link GnssNavigationMessage.Callback} object to register.
      * @param handler  the handler that the callback runs on.
      * @return {@code true} if the callback was added successfully, {@code false} otherwise.
+     *
+     * @throws IllegalArgumentException if callback is null
+     * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
      */
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public boolean registerGnssNavigationMessageCallback(
@@ -2078,6 +2137,10 @@
      * @param callback a {@link GnssNavigationMessage.Callback} object to register.
      * @param executor the looper that the callback runs on.
      * @return {@code true} if the callback was added successfully, {@code false} otherwise.
+     *
+     * @throws IllegalArgumentException if executor is null
+     * @throws IllegalArgumentException if callback is null
+     * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
      */
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public boolean registerGnssNavigationMessageCallback(
@@ -2538,7 +2601,7 @@
 
             mListenerTransport = new GnssMeasurementsListener();
             return mService.addGnssMeasurementsListener(mListenerTransport,
-                    mContext.getPackageName());
+                    mContext.getPackageName(), "gnss measurement callback");
         }
 
         @Override
@@ -2574,7 +2637,7 @@
 
             mListenerTransport = new GnssNavigationMessageListener();
             return mService.addGnssNavigationMessageListener(mListenerTransport,
-                    mContext.getPackageName());
+                    mContext.getPackageName(), "gnss navigation callback");
         }
 
         @Override
@@ -2609,7 +2672,8 @@
             Preconditions.checkState(mListenerTransport == null);
 
             mListenerTransport = new BatchedLocationCallback();
-            return mService.addGnssBatchingCallback(mListenerTransport, mContext.getPackageName());
+            return mService.addGnssBatchingCallback(mListenerTransport, mContext.getPackageName(),
+                     "batched location callback");
         }
 
         @Override
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 5341d07..33ddfa7 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -2776,7 +2776,7 @@
         updateImageSizeValues(in, IFD_TYPE_THUMBNAIL);
 
         // Check if each image data is in valid position.
-        validateImages(in);
+        validateImages();
 
         if (mMimeType == IMAGE_TYPE_PEF) {
             // PEF files contain a MakerNote data, which contains the data for ColorSpace tag.
@@ -3143,8 +3143,11 @@
         // 2.1. Integers and byte order
         in.setByteOrder(ByteOrder.BIG_ENDIAN);
 
+        int bytesRead = 0;
+
         // Skip the signature bytes
         in.seek(PNG_SIGNATURE.length);
+        bytesRead += PNG_SIGNATURE.length;
 
         try {
             while (true) {
@@ -3159,12 +3162,14 @@
                 // See PNG (Portable Network Graphics) Specification, Version 1.2,
                 // 3.2. Chunk layout
                 int length = in.readInt();
+                bytesRead += 4;
 
                 byte[] type = new byte[PNG_CHUNK_LENGTH_BYTE_LENGTH];
                 if (in.read(type) != type.length) {
                     throw new IOException("Encountered invalid length while parsing PNG chunk"
                             + "type");
                 }
+                bytesRead += PNG_CHUNK_LENGTH_BYTE_LENGTH;
 
                 if (Arrays.equals(type, PNG_CHUNK_TYPE_IEND)) {
                     // IEND marks the end of the image.
@@ -3177,12 +3182,17 @@
                                 + "type: " + byteArrayToHexString(type));
                     }
                     readExifSegment(data, IFD_TYPE_PRIMARY);
+
+                    validateImages();
                     break;
                 } else {
                     // Skip to next chunk
                     in.skipBytes(length + PNG_CHUNK_CRC_BYTE_LENGTH);
+                    bytesRead += length + PNG_CHUNK_CRC_BYTE_LENGTH;
                 }
             }
+            // Save offset values for handleThumbnailFromJfif() function
+            mExifOffset = bytesRead;
         } catch (EOFException e) {
             // Should not reach here. Will only reach here if the file is corrupted or
             // does not follow the PNG specifications
@@ -3675,7 +3685,7 @@
             int thumbnailLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder);
 
             if (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_RAF
-                    || mMimeType == IMAGE_TYPE_RW2) {
+                    || mMimeType == IMAGE_TYPE_RW2 || mMimeType == IMAGE_TYPE_PNG) {
                 thumbnailOffset += mExifOffset;
             } else if (mMimeType == IMAGE_TYPE_ORF) {
                 // Update offset value since RAF files have IFD data preceding MakerNote data.
@@ -3819,12 +3829,13 @@
     }
 
     // Validate primary, preview, thumbnail image data by comparing image size
-    private void validateImages(InputStream in) throws IOException {
+    private void validateImages() throws IOException {
         // Swap images based on size (primary > preview > thumbnail)
         swapBasedOnImageSize(IFD_TYPE_PRIMARY, IFD_TYPE_PREVIEW);
         swapBasedOnImageSize(IFD_TYPE_PRIMARY, IFD_TYPE_THUMBNAIL);
         swapBasedOnImageSize(IFD_TYPE_PREVIEW, IFD_TYPE_THUMBNAIL);
 
+        // TODO (b/142296453): Revise image width/height setting logic
         // Check if image has PixelXDimension/PixelYDimension tags, which contain valid image
         // sizes, excluding padding at the right end or bottom end of the image to make sure that
         // the values are multiples of 64. See JEITA CP-3451C Table 5 and Section 4.8.1. B.
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index f132cef..66764c7 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -27,4 +27,6 @@
     void selectRoute(String packageName, String id);
     void unselectRoute(String packageName, String id);
     void notifyControlRequestSent(String id, in Intent request);
+    void requestSetVolume(String id, int volume);
+    void requestUpdateVolume(String id, int delta);
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 81213b9..7b7a34e 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -46,6 +46,8 @@
     void registerClient2(IMediaRouter2Client client, String packageName);
     void unregisterClient2(IMediaRouter2Client client);
     void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route, in Intent request);
+    void requestSetVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int volume);
+    void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction);
     /**
      * Changes the selected route of the client.
      *
@@ -66,4 +68,9 @@
      */
     void selectClientRoute2(IMediaRouter2Manager manager, String packageName,
             in @nullable MediaRoute2Info route);
+
+    void requestSetVolume2Manager(IMediaRouter2Manager manager,
+            in MediaRoute2Info route, int volume);
+    void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
+            in MediaRoute2Info route, int direction);
 }
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 26e7936..91d644b 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -21,6 +21,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -3750,8 +3751,11 @@
         public static final int DolbyVisionProfileDvheStn = 0x20;
         public static final int DolbyVisionProfileDvheDth = 0x40;
         public static final int DolbyVisionProfileDvheDtb = 0x80;
-        public static final int DolbyVisionProfileDvheSt = 0x100;
-        public static final int DolbyVisionProfileDvavSe = 0x200;
+        public static final int DolbyVisionProfileDvheSt  = 0x100;
+        public static final int DolbyVisionProfileDvavSe  = 0x200;
+        /** Dolby Vision AV1 profile */
+        @SuppressLint("AllUpper")
+        public static final int DolbyVisionProfileDvav110 = 0x400;
 
         public static final int DolbyVisionLevelHd24    = 0x1;
         public static final int DolbyVisionLevelHd30    = 0x2;
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index f421029..7ed431d 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -1028,8 +1028,6 @@
      * @see MediaFormat#COLOR_STANDARD_BT601_PAL
      * @see MediaFormat#COLOR_STANDARD_BT601_NTSC
      * @see MediaFormat#COLOR_STANDARD_BT2020
-     *
-     * @hide
      */
     public static final int METADATA_KEY_COLOR_STANDARD = 35;
 
@@ -1040,8 +1038,6 @@
      * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
      * @see MediaFormat#COLOR_TRANSFER_ST2084
      * @see MediaFormat#COLOR_TRANSFER_HLG
-     *
-     * @hide
      */
     public static final int METADATA_KEY_COLOR_TRANSFER = 36;
 
@@ -1050,8 +1046,6 @@
      *
      * @see MediaFormat#COLOR_RANGE_LIMITED
      * @see MediaFormat#COLOR_RANGE_FULL
-     *
-     * @hide
      */
     public static final int METADATA_KEY_COLOR_RANGE    = 37;
     // Add more here...
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index e8e0f82..58deff2 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -81,6 +81,20 @@
     public abstract void onControlRequest(String routeId, Intent request);
 
     /**
+     * Called when requestSetVolume is called on a route of the provider
+     * @param routeId the id of the route
+     * @param volume the target volume
+     */
+    public abstract void onSetVolume(String routeId, int volume);
+
+    /**
+     * Called when requestUpdateVolume is called on a route of the provider
+     * @param routeId id of the route
+     * @param delta the delta to add to the current volume
+     */
+    public abstract void onUpdateVolume(String routeId, int delta);
+
+    /**
      * Updates provider info and publishes routes
      */
     public final void setProviderInfo(MediaRoute2ProviderInfo info) {
@@ -130,5 +144,17 @@
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onControlRequest,
                     MediaRoute2ProviderService.this, id, request));
         }
+
+        @Override
+        public void requestSetVolume(String id, int volume) {
+            mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetVolume,
+                    MediaRoute2ProviderService.this, id, volume));
+        }
+
+        @Override
+        public void requestUpdateVolume(String id, int delta) {
+            mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUpdateVolume,
+                    MediaRoute2ProviderService.this, id, delta));
+        }
     }
 }
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index ed35ef6..aca40d8 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -265,6 +265,54 @@
         }
     }
 
+    /**
+     * Requests a volume change for the route asynchronously.
+     * <p>
+     * It may have no effect if the route is currently not selected.
+     * </p>
+     *
+     * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
+     */
+    public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) {
+        Objects.requireNonNull(route, "route must not be null");
+
+        Client client;
+        synchronized (sLock) {
+            client = mClient;
+        }
+        if (client != null) {
+            try {
+                mMediaRouterService.requestSetVolume2(client, route, volume);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Unable to send control request.", ex);
+            }
+        }
+    }
+
+    /**
+     * Requests an incremental volume update  for the route asynchronously.
+     * <p>
+     * It may have no effect if the route is currently not selected.
+     * </p>
+     *
+     * @param delta The delta to add to the current volume.
+     */
+    public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) {
+        Objects.requireNonNull(route, "route must not be null");
+
+        Client client;
+        synchronized (sLock) {
+            client = mClient;
+        }
+        if (client != null) {
+            try {
+                mMediaRouterService.requestUpdateVolume2(client, route, delta);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Unable to send control request.", ex);
+            }
+        }
+    }
+
     @GuardedBy("mCallbackRecords")
     private int findCallbackRecordIndexLocked(Callback callback) {
         final int count = mCallbackRecords.size();
@@ -310,6 +358,7 @@
             List<MediaRoute2Info> outRoutes) {
         if (provider == null || !provider.isValid()) {
             Log.w(TAG, "Ignoring invalid provider : " + provider);
+            return;
         }
 
         final Collection<MediaRoute2Info> routes = provider.getRoutes();
@@ -321,10 +370,21 @@
             if (!route.supportsControlCategory(controlCategories)) {
                 continue;
             }
+            MediaRoute2Info preRoute = findRouteById(route.getId());
+            if (!route.equals(preRoute)) {
+                notifyRouteChanged(route);
+            }
             outRoutes.add(route);
         }
     }
 
+    MediaRoute2Info findRouteById(String id) {
+        for (MediaRoute2Info route : mRoutes) {
+            if (route.getId().equals(id)) return route;
+        }
+        return null;
+    }
+
     void notifyRouteListChanged(List<MediaRoute2Info> routes) {
         for (CallbackRecord record: mCallbackRecords) {
             record.mExecutor.execute(
@@ -332,10 +392,18 @@
         }
     }
 
+    void notifyRouteChanged(MediaRoute2Info route) {
+        for (CallbackRecord record: mCallbackRecords) {
+            record.mExecutor.execute(
+                    () -> record.mCallback.onRouteChanged(route));
+        }
+    }
+
     /**
      * Interface for receiving events about media routing changes.
      */
     public static class Callback {
+        //TODO: clean up these callbacks
         /**
          * Called when a route is added.
          */
@@ -369,7 +437,7 @@
         void notifyRoutes() {
             final List<MediaRoute2Info> routes = mRoutes;
             // notify only when bound to media router service.
-            //TODO: Correct the condition when control category, default rotue, .. are finalized.
+            //TODO: Correct the condition when control category, default route, .. are finalized.
             if (routes.size() > 0) {
                 mExecutor.execute(() -> mCallback.onRoutesChanged(routes));
             }
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 0b64569..4f2a295 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -234,6 +234,54 @@
         }
     }
 
+    /**
+     * Requests a volume change for the route asynchronously.
+     * <p>
+     * It may have no effect if the route is currently not selected.
+     * </p>
+     *
+     * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
+     */
+    public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) {
+        Objects.requireNonNull(route, "route must not be null");
+
+        Client client;
+        synchronized (sLock) {
+            client = mClient;
+        }
+        if (client != null) {
+            try {
+                mMediaRouterService.requestSetVolume2Manager(client, route, volume);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Unable to send control request.", ex);
+            }
+        }
+    }
+
+    /**
+     * Requests an incremental volume update  for the route asynchronously.
+     * <p>
+     * It may have no effect if the route is currently not selected.
+     * </p>
+     *
+     * @param delta The delta to add to the current volume.
+     */
+    public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) {
+        Objects.requireNonNull(route, "route must not be null");
+
+        Client client;
+        synchronized (sLock) {
+            client = mClient;
+        }
+        if (client != null) {
+            try {
+                mMediaRouterService.requestUpdateVolume2Manager(client, route, delta);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Unable to send control request.", ex);
+            }
+        }
+    }
+
     int findProviderIndex(MediaRoute2ProviderInfo provider) {
         final int count = mProviders.size();
         for (int i = 0; i < count; i++) {
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 771628c..ca96c9a 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -17,106 +17,14 @@
 package android.media;
 
 import android.annotation.UnsupportedAppUsage;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.drm.DrmManagerClient;
-import android.graphics.BitmapFactory;
-import android.mtp.MtpConstants;
 import android.net.Uri;
 import android.os.Build;
-import android.os.Environment;
 import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio;
-import android.provider.MediaStore.Audio.Playlists;
-import android.provider.MediaStore.Files;
-import android.provider.MediaStore.Files.FileColumns;
-import android.provider.MediaStore.Images;
-import android.provider.MediaStore.Video;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-import android.sax.Element;
-import android.sax.ElementListener;
-import android.sax.RootElement;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Xml;
-
-import dalvik.system.CloseGuard;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.SAXException;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Locale;
-import java.util.TimeZone;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
- * Internal service helper that no-one should use directly.
- *
- * The way the scan currently works is:
- * - The Java MediaScannerService creates a MediaScanner (this class), and calls
- *   MediaScanner.scanDirectories on it.
- * - scanDirectories() calls the native processDirectory() for each of the specified directories.
- * - the processDirectory() JNI method wraps the provided mediascanner client in a native
- *   'MyMediaScannerClient' class, then calls processDirectory() on the native MediaScanner
- *   object (which got created when the Java MediaScanner was created).
- * - native MediaScanner.processDirectory() calls
- *   doProcessDirectory(), which recurses over the folder, and calls
- *   native MyMediaScannerClient.scanFile() for every file whose extension matches.
- * - native MyMediaScannerClient.scanFile() calls back on Java MediaScannerClient.scanFile,
- *   which calls doScanFile, which after some setup calls back down to native code, calling
- *   MediaScanner.processFile().
- * - MediaScanner.processFile() calls one of several methods, depending on the type of the
- *   file: parseMP3, parseMP4, parseMidi, parseOgg or parseWMA.
- * - each of these methods gets metadata key/value pairs from the file, and repeatedly
- *   calls native MyMediaScannerClient.handleStringTag, which calls back up to its Java
- *   counterparts in this file.
- * - Java handleStringTag() gathers the key/value pairs that it's interested in.
- * - once processFile returns and we're back in Java code in doScanFile(), it calls
- *   Java MyMediaScannerClient.endFile(), which takes all the data that's been
- *   gathered and inserts an entry in to the database.
- *
- * In summary:
- * Java MediaScannerService calls
- * Java MediaScanner scanDirectories, which calls
- * Java MediaScanner processDirectory (native method), which calls
- * native MediaScanner processDirectory, which calls
- * native MyMediaScannerClient scanFile, which calls
- * Java MyMediaScannerClient scanFile, which calls
- * Java MediaScannerClient doScanFile, which calls
- * Java MediaScanner processFile (native method), which calls
- * native MediaScanner processFile, which calls
- * native parseMP3, parseMP4, parseMidi, parseOgg or parseWMA, which calls
- * native MyMediaScanner handleStringTag, which calls
- * Java MyMediaScanner handleStringTag.
- * Once MediaScanner processFile returns, an entry is inserted in to the database.
- *
- * The MediaScanner class is not thread-safe, so it should only be used in a single threaded manner.
- *
- * {@hide}
- *
+ * @hide
  * @deprecated this media scanner has served faithfully for many years, but it's
  *             become tedious to test and maintain, mainly due to the way it
  *             weaves obscurely between managed and native code. It's been
@@ -125,1876 +33,182 @@
  */
 @Deprecated
 public class MediaScanner implements AutoCloseable {
-    static {
-        System.loadLibrary("media_jni");
-        native_init();
-    }
-
-    private final static String TAG = "MediaScanner";
-
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     private static final String[] FILES_PRESCAN_PROJECTION = new String[] {
-            Files.FileColumns._ID, // 0
-            Files.FileColumns.DATA, // 1
-            Files.FileColumns.FORMAT, // 2
-            Files.FileColumns.DATE_MODIFIED, // 3
-            Files.FileColumns.MEDIA_TYPE, // 4
     };
 
-    private static final String[] ID_PROJECTION = new String[] {
-            Files.FileColumns._ID,
-    };
-
-    private static final int FILES_PRESCAN_ID_COLUMN_INDEX = 0;
-    private static final int FILES_PRESCAN_PATH_COLUMN_INDEX = 1;
-    private static final int FILES_PRESCAN_FORMAT_COLUMN_INDEX = 2;
-    private static final int FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX = 3;
-    private static final int FILES_PRESCAN_MEDIA_TYPE_COLUMN_INDEX = 4;
-
-    private static final String[] PLAYLIST_MEMBERS_PROJECTION = new String[] {
-            Audio.Playlists.Members.PLAYLIST_ID, // 0
-     };
-
-    private static final int ID_PLAYLISTS_COLUMN_INDEX = 0;
-    private static final int PATH_PLAYLISTS_COLUMN_INDEX = 1;
-    private static final int DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX = 2;
-
-    private static final String RINGTONES_DIR = "/ringtones/";
-    private static final String NOTIFICATIONS_DIR = "/notifications/";
-    private static final String ALARMS_DIR = "/alarms/";
-    private static final String MUSIC_DIR = "/music/";
-    private static final String PODCASTS_DIR = "/podcasts/";
-    private static final String AUDIOBOOKS_DIR = "/audiobooks/";
-
-    public static final String SCANNED_BUILD_PREFS_NAME = "MediaScanBuild";
-    public static final String LAST_INTERNAL_SCAN_FINGERPRINT = "lastScanFingerprint";
-    private static final String SYSTEM_SOUNDS_DIR = Environment.getRootDirectory() + "/media/audio";
-    private static final String OEM_SOUNDS_DIR = Environment.getOemDirectory() + "/media/audio";
-    private static final String PRODUCT_SOUNDS_DIR = Environment.getProductDirectory() + "/media/audio";
-    private static String sLastInternalScanFingerprint;
-
-    private static final String[] ID3_GENRES = {
-        // ID3v1 Genres
-        "Blues",
-        "Classic Rock",
-        "Country",
-        "Dance",
-        "Disco",
-        "Funk",
-        "Grunge",
-        "Hip-Hop",
-        "Jazz",
-        "Metal",
-        "New Age",
-        "Oldies",
-        "Other",
-        "Pop",
-        "R&B",
-        "Rap",
-        "Reggae",
-        "Rock",
-        "Techno",
-        "Industrial",
-        "Alternative",
-        "Ska",
-        "Death Metal",
-        "Pranks",
-        "Soundtrack",
-        "Euro-Techno",
-        "Ambient",
-        "Trip-Hop",
-        "Vocal",
-        "Jazz+Funk",
-        "Fusion",
-        "Trance",
-        "Classical",
-        "Instrumental",
-        "Acid",
-        "House",
-        "Game",
-        "Sound Clip",
-        "Gospel",
-        "Noise",
-        "AlternRock",
-        "Bass",
-        "Soul",
-        "Punk",
-        "Space",
-        "Meditative",
-        "Instrumental Pop",
-        "Instrumental Rock",
-        "Ethnic",
-        "Gothic",
-        "Darkwave",
-        "Techno-Industrial",
-        "Electronic",
-        "Pop-Folk",
-        "Eurodance",
-        "Dream",
-        "Southern Rock",
-        "Comedy",
-        "Cult",
-        "Gangsta",
-        "Top 40",
-        "Christian Rap",
-        "Pop/Funk",
-        "Jungle",
-        "Native American",
-        "Cabaret",
-        "New Wave",
-        "Psychadelic",
-        "Rave",
-        "Showtunes",
-        "Trailer",
-        "Lo-Fi",
-        "Tribal",
-        "Acid Punk",
-        "Acid Jazz",
-        "Polka",
-        "Retro",
-        "Musical",
-        "Rock & Roll",
-        "Hard Rock",
-        // The following genres are Winamp extensions
-        "Folk",
-        "Folk-Rock",
-        "National Folk",
-        "Swing",
-        "Fast Fusion",
-        "Bebob",
-        "Latin",
-        "Revival",
-        "Celtic",
-        "Bluegrass",
-        "Avantgarde",
-        "Gothic Rock",
-        "Progressive Rock",
-        "Psychedelic Rock",
-        "Symphonic Rock",
-        "Slow Rock",
-        "Big Band",
-        "Chorus",
-        "Easy Listening",
-        "Acoustic",
-        "Humour",
-        "Speech",
-        "Chanson",
-        "Opera",
-        "Chamber Music",
-        "Sonata",
-        "Symphony",
-        "Booty Bass",
-        "Primus",
-        "Porn Groove",
-        "Satire",
-        "Slow Jam",
-        "Club",
-        "Tango",
-        "Samba",
-        "Folklore",
-        "Ballad",
-        "Power Ballad",
-        "Rhythmic Soul",
-        "Freestyle",
-        "Duet",
-        "Punk Rock",
-        "Drum Solo",
-        "A capella",
-        "Euro-House",
-        "Dance Hall",
-        // The following ones seem to be fairly widely supported as well
-        "Goa",
-        "Drum & Bass",
-        "Club-House",
-        "Hardcore",
-        "Terror",
-        "Indie",
-        "Britpop",
-        null,
-        "Polsk Punk",
-        "Beat",
-        "Christian Gangsta",
-        "Heavy Metal",
-        "Black Metal",
-        "Crossover",
-        "Contemporary Christian",
-        "Christian Rock",
-        "Merengue",
-        "Salsa",
-        "Thrash Metal",
-        "Anime",
-        "JPop",
-        "Synthpop",
-        // 148 and up don't seem to have been defined yet.
-    };
-
-    private long mNativeContext;
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     private final Context mContext;
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     private final String mPackageName;
-    private final String mVolumeName;
-    private final ContentProviderClient mMediaProvider;
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     private final Uri mAudioUri;
-    private final Uri mVideoUri;
-    private final Uri mImagesUri;
-    private final Uri mPlaylistsUri;
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     private final Uri mFilesUri;
-    private final Uri mFilesFullUri;
-    private final boolean mProcessPlaylists;
-    private final boolean mProcessGenres;
-    private int mMtpObjectHandle;
 
-    private final AtomicBoolean mClosed = new AtomicBoolean();
-    private final CloseGuard mCloseGuard = CloseGuard.get();
-
-    /** whether to use bulk inserts or individual inserts for each item */
-    private static final boolean ENABLE_BULK_INSERTS = true;
-
-    // used when scanning the image database so we know whether we have to prune
-    // old thumbnail files
-    private int mOriginalCount;
-    /** Whether the scanner has set a default sound for the ringer ringtone. */
-    private boolean mDefaultRingtoneSet;
-    /** Whether the scanner has set a default sound for the notification ringtone. */
-    private boolean mDefaultNotificationSet;
-    /** Whether the scanner has set a default sound for the alarm ringtone. */
-    private boolean mDefaultAlarmSet;
-    /** The filename for the default sound for the ringer ringtone. */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     private String mDefaultRingtoneFilename;
-    /** The filename for the default sound for the notification ringtone. */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     private String mDefaultNotificationFilename;
-    /** The filename for the default sound for the alarm ringtone. */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     private String mDefaultAlarmAlertFilename;
-    /**
-     * The prefix for system properties that define the default sound for
-     * ringtones. Concatenate the name of the setting from Settings
-     * to get the full system property.
-     */
-    private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config.";
-
-    private final BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
 
     private static class FileEntry {
-        @UnsupportedAppUsage
+        @Deprecated
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         long mRowId;
-        String mPath;
-        long mLastModified;
-        int mFormat;
-        int mMediaType;
-        @UnsupportedAppUsage
+        @Deprecated
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         boolean mLastModifiedChanged;
 
-        /** @deprecated kept intact for lame apps using reflection */
         @Deprecated
-        @UnsupportedAppUsage
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         FileEntry(long rowId, String path, long lastModified, int format) {
-            this(rowId, path, lastModified, format, FileColumns.MEDIA_TYPE_NONE);
-        }
-
-        FileEntry(long rowId, String path, long lastModified, int format, int mediaType) {
-            mRowId = rowId;
-            mPath = path;
-            mLastModified = lastModified;
-            mFormat = format;
-            mMediaType = mediaType;
-            mLastModifiedChanged = false;
-        }
-
-        @Override
-        public String toString() {
-            return mPath + " mRowId: " + mRowId;
+            throw new UnsupportedOperationException();
         }
     }
 
-    private static class PlaylistEntry {
-        String path;
-        long bestmatchid;
-        int bestmatchlevel;
-    }
-
-    private final ArrayList<PlaylistEntry> mPlaylistEntries = new ArrayList<>();
-    private final ArrayList<FileEntry> mPlayLists = new ArrayList<>();
-
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     private MediaInserter mMediaInserter;
 
-    private DrmManagerClient mDrmManagerClient = null;
-
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     public MediaScanner(Context c, String volumeName) {
-        native_setup();
-        mContext = c;
-        mPackageName = c.getPackageName();
-        mVolumeName = volumeName;
-
-        mBitmapOptions.inSampleSize = 1;
-        mBitmapOptions.inJustDecodeBounds = true;
-
-        setDefaultRingtoneFileNames();
-
-        mMediaProvider = mContext.getContentResolver()
-                .acquireContentProviderClient(MediaStore.AUTHORITY);
-
-        if (sLastInternalScanFingerprint == null) {
-            final SharedPreferences scanSettings =
-                    mContext.getSharedPreferences(SCANNED_BUILD_PREFS_NAME, Context.MODE_PRIVATE);
-            sLastInternalScanFingerprint =
-                    scanSettings.getString(LAST_INTERNAL_SCAN_FINGERPRINT, new String());
-        }
-
-        mAudioUri = Audio.Media.getContentUri(volumeName);
-        mVideoUri = Video.Media.getContentUri(volumeName);
-        mImagesUri = Images.Media.getContentUri(volumeName);
-        mFilesUri = Files.getContentUri(volumeName);
-
-        Uri filesFullUri = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
-        filesFullUri = MediaStore.setIncludePending(filesFullUri);
-        filesFullUri = MediaStore.setIncludeTrashed(filesFullUri);
-        mFilesFullUri = filesFullUri;
-
-        if (!volumeName.equals("internal")) {
-            // we only support playlists on external media
-            mProcessPlaylists = true;
-            mProcessGenres = true;
-            mPlaylistsUri = Playlists.getContentUri(volumeName);
-        } else {
-            mProcessPlaylists = false;
-            mProcessGenres = false;
-            mPlaylistsUri = null;
-        }
-
-        final Locale locale = mContext.getResources().getConfiguration().locale;
-        if (locale != null) {
-            String language = locale.getLanguage();
-            String country = locale.getCountry();
-            if (language != null) {
-                if (country != null) {
-                    setLocale(language + "_" + country);
-                } else {
-                    setLocale(language);
-                }
-            }
-        }
-
-        mCloseGuard.open("close");
+        throw new UnsupportedOperationException();
     }
 
-    private void setDefaultRingtoneFileNames() {
-        mDefaultRingtoneFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
-                + Settings.System.RINGTONE);
-        mDefaultNotificationFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
-                + Settings.System.NOTIFICATION_SOUND);
-        mDefaultAlarmAlertFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
-                + Settings.System.ALARM_ALERT);
-    }
-
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     private final MyMediaScannerClient mClient = new MyMediaScannerClient();
 
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     private boolean isDrmEnabled() {
-        String prop = SystemProperties.get("drm.service.enabled");
-        return prop != null && prop.equals("true");
+        throw new UnsupportedOperationException();
     }
 
     private class MyMediaScannerClient implements MediaScannerClient {
-
-        private final SimpleDateFormat mDateFormatter;
-
-        private String mArtist;
-        private String mAlbumArtist;    // use this if mArtist is missing
-        private String mAlbum;
-        private String mTitle;
-        private String mComposer;
-        private String mGenre;
-        @UnsupportedAppUsage
-        private String mMimeType;
-        /** @deprecated file types no longer exist */
         @Deprecated
-        @UnsupportedAppUsage
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
+        private String mMimeType;
+        @Deprecated
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         private int mFileType;
-        private int mTrack;
-        private int mYear;
-        private int mDuration;
-        @UnsupportedAppUsage
+        @Deprecated
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         private String mPath;
-        private long mDate;
-        private long mLastModified;
-        private long mFileSize;
-        private String mWriter;
-        private int mCompilation;
-        @UnsupportedAppUsage
+        @Deprecated
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         private boolean mIsDrm;
-        @UnsupportedAppUsage
-        private boolean mNoMedia;   // flag to suppress file from appearing in media tables
-        private boolean mScanSuccess;
-        private int mWidth;
-        private int mHeight;
-        private int mColorStandard;
-        private int mColorTransfer;
-        private int mColorRange;
+        @Deprecated
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
+        private boolean mNoMedia;
 
         public MyMediaScannerClient() {
-            mDateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
-            mDateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+            throw new UnsupportedOperationException();
         }
 
-        @UnsupportedAppUsage
+        @Deprecated
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         public FileEntry beginFile(String path, String mimeType, long lastModified,
                 long fileSize, boolean isDirectory, boolean noMedia) {
-            mMimeType = mimeType;
-            mFileSize = fileSize;
-            mIsDrm = false;
-            mScanSuccess = true;
-
-            if (!isDirectory) {
-                if (!noMedia && isNoMediaFile(path)) {
-                    noMedia = true;
-                }
-                mNoMedia = noMedia;
-
-                // if mimeType was not specified, compute file type based on file extension.
-                if (mMimeType == null) {
-                    mMimeType = MediaFile.getMimeTypeForFile(path);
-                }
-
-                if (isDrmEnabled() && MediaFile.isDrmMimeType(mMimeType)) {
-                    getMimeTypeFromDrm(path);
-                }
-            }
-
-            FileEntry entry = makeEntryFor(path);
-            // add some slack to avoid a rounding error
-            long delta = (entry != null) ? (lastModified - entry.mLastModified) : 0;
-            boolean wasModified = delta > 1 || delta < -1;
-            if (entry == null || wasModified) {
-                if (wasModified) {
-                    entry.mLastModified = lastModified;
-                } else {
-                    entry = new FileEntry(0, path, lastModified,
-                            (isDirectory ? MtpConstants.FORMAT_ASSOCIATION : 0),
-                            FileColumns.MEDIA_TYPE_NONE);
-                }
-                entry.mLastModifiedChanged = true;
-            }
-
-            if (mProcessPlaylists && MediaFile.isPlayListMimeType(mMimeType)) {
-                mPlayLists.add(entry);
-                // we don't process playlists in the main scan, so return null
-                return null;
-            }
-
-            // clear all the metadata
-            mArtist = null;
-            mAlbumArtist = null;
-            mAlbum = null;
-            mTitle = null;
-            mComposer = null;
-            mGenre = null;
-            mTrack = 0;
-            mYear = 0;
-            mDuration = 0;
-            mPath = path;
-            mDate = 0;
-            mLastModified = lastModified;
-            mWriter = null;
-            mCompilation = 0;
-            mWidth = 0;
-            mHeight = 0;
-            mColorStandard = -1;
-            mColorTransfer = -1;
-            mColorRange = -1;
-
-            return entry;
+            throw new UnsupportedOperationException();
         }
 
-        @Override
-        @UnsupportedAppUsage
+        @Deprecated
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         public void scanFile(String path, long lastModified, long fileSize,
                 boolean isDirectory, boolean noMedia) {
-            // This is the callback funtion from native codes.
-            // Log.v(TAG, "scanFile: "+path);
-            doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia);
+            throw new UnsupportedOperationException();
         }
 
-        @UnsupportedAppUsage
+        @Deprecated
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         public Uri doScanFile(String path, String mimeType, long lastModified,
                 long fileSize, boolean isDirectory, boolean scanAlways, boolean noMedia) {
-            Uri result = null;
-//            long t1 = System.currentTimeMillis();
-            try {
-                FileEntry entry = beginFile(path, mimeType, lastModified,
-                        fileSize, isDirectory, noMedia);
-
-                if (entry == null) {
-                    return null;
-                }
-
-                // if this file was just inserted via mtp, set the rowid to zero
-                // (even though it already exists in the database), to trigger
-                // the correct code path for updating its entry
-                if (mMtpObjectHandle != 0) {
-                    entry.mRowId = 0;
-                }
-
-                if (entry.mPath != null) {
-                    if (((!mDefaultNotificationSet &&
-                                doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename))
-                        || (!mDefaultRingtoneSet &&
-                                doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename))
-                        || (!mDefaultAlarmSet &&
-                                doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)))) {
-                        Log.w(TAG, "forcing rescan of " + entry.mPath +
-                                "since ringtone setting didn't finish");
-                        scanAlways = true;
-                    } else if (isSystemSoundWithMetadata(entry.mPath)
-                            && !Build.FINGERPRINT.equals(sLastInternalScanFingerprint)) {
-                        // file is located on the system partition where the date cannot be trusted:
-                        // rescan if the build fingerprint has changed since the last scan.
-                        Log.i(TAG, "forcing rescan of " + entry.mPath
-                                + " since build fingerprint changed");
-                        scanAlways = true;
-                    }
-                }
-
-                // rescan for metadata if file was modified since last scan
-                if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
-                    if (noMedia) {
-                        result = endFile(entry, false, false, false, false, false, false);
-                    } else {
-                        boolean isaudio = MediaFile.isAudioMimeType(mMimeType);
-                        boolean isvideo = MediaFile.isVideoMimeType(mMimeType);
-                        boolean isimage = MediaFile.isImageMimeType(mMimeType);
-
-                        if (isaudio || isvideo || isimage) {
-                            path = Environment.maybeTranslateEmulatedPathToInternal(new File(path))
-                                    .getAbsolutePath();
-                        }
-
-                        // we only extract metadata for audio and video files
-                        if (isaudio || isvideo) {
-                            mScanSuccess = processFile(path, mimeType, this);
-                        }
-
-                        if (isimage) {
-                            mScanSuccess = processImageFile(path);
-                        }
-
-                        String lowpath = path.toLowerCase(Locale.ROOT);
-                        boolean ringtones = mScanSuccess && (lowpath.indexOf(RINGTONES_DIR) > 0);
-                        boolean notifications = mScanSuccess &&
-                                (lowpath.indexOf(NOTIFICATIONS_DIR) > 0);
-                        boolean alarms = mScanSuccess && (lowpath.indexOf(ALARMS_DIR) > 0);
-                        boolean podcasts = mScanSuccess && (lowpath.indexOf(PODCASTS_DIR) > 0);
-                        boolean audiobooks = mScanSuccess && (lowpath.indexOf(AUDIOBOOKS_DIR) > 0);
-                        boolean music = mScanSuccess && ((lowpath.indexOf(MUSIC_DIR) > 0) ||
-                            (!ringtones && !notifications && !alarms && !podcasts && !audiobooks));
-
-                        result = endFile(entry, ringtones, notifications, alarms, podcasts,
-                                audiobooks, music);
-                    }
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
-            }
-//            long t2 = System.currentTimeMillis();
-//            Log.v(TAG, "scanFile: " + path + " took " + (t2-t1));
-            return result;
+            throw new UnsupportedOperationException();
         }
 
-        private long parseDate(String date) {
-            try {
-              return mDateFormatter.parse(date).getTime();
-            } catch (ParseException e) {
-              return 0;
-            }
-        }
-
-        private int parseSubstring(String s, int start, int defaultValue) {
-            int length = s.length();
-            if (start == length) return defaultValue;
-
-            char ch = s.charAt(start++);
-            // return defaultValue if we have no integer at all
-            if (ch < '0' || ch > '9') return defaultValue;
-
-            int result = ch - '0';
-            while (start < length) {
-                ch = s.charAt(start++);
-                if (ch < '0' || ch > '9') return result;
-                result = result * 10 + (ch - '0');
-            }
-
-            return result;
-        }
-
-        @UnsupportedAppUsage
+        @Deprecated
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         public void handleStringTag(String name, String value) {
-            if (name.equalsIgnoreCase("title") || name.startsWith("title;")) {
-                // Don't trim() here, to preserve the special \001 character
-                // used to force sorting. The media provider will trim() before
-                // inserting the title in to the database.
-                mTitle = value;
-            } else if (name.equalsIgnoreCase("artist") || name.startsWith("artist;")) {
-                mArtist = value.trim();
-            } else if (name.equalsIgnoreCase("albumartist") || name.startsWith("albumartist;")
-                    || name.equalsIgnoreCase("band") || name.startsWith("band;")) {
-                mAlbumArtist = value.trim();
-            } else if (name.equalsIgnoreCase("album") || name.startsWith("album;")) {
-                mAlbum = value.trim();
-            } else if (name.equalsIgnoreCase("composer") || name.startsWith("composer;")) {
-                mComposer = value.trim();
-            } else if (mProcessGenres &&
-                    (name.equalsIgnoreCase("genre") || name.startsWith("genre;"))) {
-                mGenre = getGenreName(value);
-            } else if (name.equalsIgnoreCase("year") || name.startsWith("year;")) {
-                mYear = parseSubstring(value, 0, 0);
-            } else if (name.equalsIgnoreCase("tracknumber") || name.startsWith("tracknumber;")) {
-                // track number might be of the form "2/12"
-                // we just read the number before the slash
-                int num = parseSubstring(value, 0, 0);
-                mTrack = (mTrack / 1000) * 1000 + num;
-            } else if (name.equalsIgnoreCase("discnumber") ||
-                    name.equals("set") || name.startsWith("set;")) {
-                // set number might be of the form "1/3"
-                // we just read the number before the slash
-                int num = parseSubstring(value, 0, 0);
-                mTrack = (num * 1000) + (mTrack % 1000);
-            } else if (name.equalsIgnoreCase("duration")) {
-                mDuration = parseSubstring(value, 0, 0);
-            } else if (name.equalsIgnoreCase("writer") || name.startsWith("writer;")) {
-                mWriter = value.trim();
-            } else if (name.equalsIgnoreCase("compilation")) {
-                mCompilation = parseSubstring(value, 0, 0);
-            } else if (name.equalsIgnoreCase("isdrm")) {
-                mIsDrm = (parseSubstring(value, 0, 0) == 1);
-            } else if (name.equalsIgnoreCase("date")) {
-                mDate = parseDate(value);
-            } else if (name.equalsIgnoreCase("width")) {
-                mWidth = parseSubstring(value, 0, 0);
-            } else if (name.equalsIgnoreCase("height")) {
-                mHeight = parseSubstring(value, 0, 0);
-            } else if (name.equalsIgnoreCase("colorstandard")) {
-                mColorStandard = parseSubstring(value, 0, -1);
-            } else if (name.equalsIgnoreCase("colortransfer")) {
-                mColorTransfer = parseSubstring(value, 0, -1);
-            } else if (name.equalsIgnoreCase("colorrange")) {
-                mColorRange = parseSubstring(value, 0, -1);
-            } else {
-                //Log.v(TAG, "unknown tag: " + name + " (" + mProcessGenres + ")");
-            }
+            throw new UnsupportedOperationException();
         }
 
-        private boolean convertGenreCode(String input, String expected) {
-            String output = getGenreName(input);
-            if (output.equals(expected)) {
-                return true;
-            } else {
-                Log.d(TAG, "'" + input + "' -> '" + output + "', expected '" + expected + "'");
-                return false;
-            }
-        }
-        private void testGenreNameConverter() {
-            convertGenreCode("2", "Country");
-            convertGenreCode("(2)", "Country");
-            convertGenreCode("(2", "(2");
-            convertGenreCode("2 Foo", "Country");
-            convertGenreCode("(2) Foo", "Country");
-            convertGenreCode("(2 Foo", "(2 Foo");
-            convertGenreCode("2Foo", "2Foo");
-            convertGenreCode("(2)Foo", "Country");
-            convertGenreCode("200 Foo", "Foo");
-            convertGenreCode("(200) Foo", "Foo");
-            convertGenreCode("200Foo", "200Foo");
-            convertGenreCode("(200)Foo", "Foo");
-            convertGenreCode("200)Foo", "200)Foo");
-            convertGenreCode("200) Foo", "200) Foo");
-        }
-
-        public String getGenreName(String genreTagValue) {
-
-            if (genreTagValue == null) {
-                return null;
-            }
-            final int length = genreTagValue.length();
-
-            if (length > 0) {
-                boolean parenthesized = false;
-                StringBuffer number = new StringBuffer();
-                int i = 0;
-                for (; i < length; ++i) {
-                    char c = genreTagValue.charAt(i);
-                    if (i == 0 && c == '(') {
-                        parenthesized = true;
-                    } else if (Character.isDigit(c)) {
-                        number.append(c);
-                    } else {
-                        break;
-                    }
-                }
-                char charAfterNumber = i < length ? genreTagValue.charAt(i) : ' ';
-                if ((parenthesized && charAfterNumber == ')')
-                        || !parenthesized && Character.isWhitespace(charAfterNumber)) {
-                    try {
-                        short genreIndex = Short.parseShort(number.toString());
-                        if (genreIndex >= 0) {
-                            if (genreIndex < ID3_GENRES.length && ID3_GENRES[genreIndex] != null) {
-                                return ID3_GENRES[genreIndex];
-                            } else if (genreIndex == 0xFF) {
-                                return null;
-                            } else if (genreIndex < 0xFF && (i + 1) < length) {
-                                // genre is valid but unknown,
-                                // if there is a string after the value we take it
-                                if (parenthesized && charAfterNumber == ')') {
-                                    i++;
-                                }
-                                String ret = genreTagValue.substring(i).trim();
-                                if (ret.length() != 0) {
-                                    return ret;
-                                }
-                            } else {
-                                // else return the number, without parentheses
-                                return number.toString();
-                            }
-                        }
-                    } catch (NumberFormatException e) {
-                    }
-                }
-            }
-
-            return genreTagValue;
-        }
-
-        private boolean processImageFile(String path) {
-            try {
-                mBitmapOptions.outWidth = 0;
-                mBitmapOptions.outHeight = 0;
-                BitmapFactory.decodeFile(path, mBitmapOptions);
-                mWidth = mBitmapOptions.outWidth;
-                mHeight = mBitmapOptions.outHeight;
-                return mWidth > 0 && mHeight > 0;
-            } catch (Throwable th) {
-                // ignore;
-            }
-            return false;
-        }
-
-        @UnsupportedAppUsage
+        @Deprecated
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         public void setMimeType(String mimeType) {
-            if ("audio/mp4".equals(mMimeType) &&
-                    mimeType.startsWith("video")) {
-                // for feature parity with Donut, we force m4a files to keep the
-                // audio/mp4 mimetype, even if they are really "enhanced podcasts"
-                // with a video track
-                return;
-            }
-            mMimeType = mimeType;
+            throw new UnsupportedOperationException();
         }
 
-        /**
-         * Formats the data into a values array suitable for use with the Media
-         * Content Provider.
-         *
-         * @return a map of values
-         */
-        @UnsupportedAppUsage
+        @Deprecated
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         private ContentValues toValues() {
-            ContentValues map = new ContentValues();
-
-            map.put(MediaStore.MediaColumns.DATA, mPath);
-            map.put(MediaStore.MediaColumns.TITLE, mTitle);
-            map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified);
-            map.put(MediaStore.MediaColumns.SIZE, mFileSize);
-            map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType);
-            map.put(MediaStore.MediaColumns.IS_DRM, mIsDrm);
-            map.putNull(MediaStore.MediaColumns.HASH);
-
-            String resolution = null;
-            if (mWidth > 0 && mHeight > 0) {
-                map.put(MediaStore.MediaColumns.WIDTH, mWidth);
-                map.put(MediaStore.MediaColumns.HEIGHT, mHeight);
-                resolution = mWidth + "x" + mHeight;
-            }
-
-            if (!mNoMedia) {
-                if (MediaFile.isVideoMimeType(mMimeType)) {
-                    map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0
-                            ? mArtist : MediaStore.UNKNOWN_STRING));
-                    map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0
-                            ? mAlbum : MediaStore.UNKNOWN_STRING));
-                    map.put(Video.Media.DURATION, mDuration);
-                    if (resolution != null) {
-                        map.put(Video.Media.RESOLUTION, resolution);
-                    }
-                    if (mColorStandard >= 0) {
-                        map.put(Video.Media.COLOR_STANDARD, mColorStandard);
-                    }
-                    if (mColorTransfer >= 0) {
-                        map.put(Video.Media.COLOR_TRANSFER, mColorTransfer);
-                    }
-                    if (mColorRange >= 0) {
-                        map.put(Video.Media.COLOR_RANGE, mColorRange);
-                    }
-                    if (mDate > 0) {
-                        map.put(Video.Media.DATE_TAKEN, mDate);
-                    }
-                } else if (MediaFile.isImageMimeType(mMimeType)) {
-                    // FIXME - add DESCRIPTION
-                } else if (MediaFile.isAudioMimeType(mMimeType)) {
-                    map.put(Audio.Media.ARTIST, (mArtist != null && mArtist.length() > 0) ?
-                            mArtist : MediaStore.UNKNOWN_STRING);
-                    map.put(Audio.Media.ALBUM_ARTIST, (mAlbumArtist != null &&
-                            mAlbumArtist.length() > 0) ? mAlbumArtist : null);
-                    map.put(Audio.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0) ?
-                            mAlbum : MediaStore.UNKNOWN_STRING);
-                    map.put(Audio.Media.COMPOSER, mComposer);
-                    map.put(Audio.Media.GENRE, mGenre);
-                    if (mYear != 0) {
-                        map.put(Audio.Media.YEAR, mYear);
-                    }
-                    map.put(Audio.Media.TRACK, mTrack);
-                    map.put(Audio.Media.DURATION, mDuration);
-                    map.put(Audio.Media.COMPILATION, mCompilation);
-                }
-            }
-            return map;
+            throw new UnsupportedOperationException();
         }
 
-        @UnsupportedAppUsage
+        @Deprecated
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         private Uri endFile(FileEntry entry, boolean ringtones, boolean notifications,
                 boolean alarms, boolean podcasts, boolean audiobooks, boolean music)
                 throws RemoteException {
-            // update database
-
-            // use album artist if artist is missing
-            if (mArtist == null || mArtist.length() == 0) {
-                mArtist = mAlbumArtist;
-            }
-
-            ContentValues values = toValues();
-            String title = values.getAsString(MediaStore.MediaColumns.TITLE);
-            if (title == null || TextUtils.isEmpty(title.trim())) {
-                title = MediaFile.getFileTitle(values.getAsString(MediaStore.MediaColumns.DATA));
-                values.put(MediaStore.MediaColumns.TITLE, title);
-            }
-            String album = values.getAsString(Audio.Media.ALBUM);
-            if (MediaStore.UNKNOWN_STRING.equals(album)) {
-                album = values.getAsString(MediaStore.MediaColumns.DATA);
-                // extract last path segment before file name
-                int lastSlash = album.lastIndexOf('/');
-                if (lastSlash >= 0) {
-                    int previousSlash = 0;
-                    while (true) {
-                        int idx = album.indexOf('/', previousSlash + 1);
-                        if (idx < 0 || idx >= lastSlash) {
-                            break;
-                        }
-                        previousSlash = idx;
-                    }
-                    if (previousSlash != 0) {
-                        album = album.substring(previousSlash + 1, lastSlash);
-                        values.put(Audio.Media.ALBUM, album);
-                    }
-                }
-            }
-            long rowId = entry.mRowId;
-            if (MediaFile.isAudioMimeType(mMimeType) && (rowId == 0 || mMtpObjectHandle != 0)) {
-                // Only set these for new entries. For existing entries, they
-                // may have been modified later, and we want to keep the current
-                // values so that custom ringtones still show up in the ringtone
-                // picker.
-                values.put(Audio.Media.IS_RINGTONE, ringtones);
-                values.put(Audio.Media.IS_NOTIFICATION, notifications);
-                values.put(Audio.Media.IS_ALARM, alarms);
-                values.put(Audio.Media.IS_MUSIC, music);
-                values.put(Audio.Media.IS_PODCAST, podcasts);
-                values.put(Audio.Media.IS_AUDIOBOOK, audiobooks);
-            } else if (MediaFile.isExifMimeType(mMimeType) && !mNoMedia) {
-                ExifInterface exif = null;
-                try {
-                    exif = new ExifInterface(entry.mPath);
-                } catch (Exception ex) {
-                    // exif is null
-                }
-                if (exif != null) {
-                    long time = exif.getGpsDateTime();
-                    if (time != -1) {
-                        values.put(Images.Media.DATE_TAKEN, time);
-                    } else {
-                        // If no time zone information is available, we should consider using
-                        // EXIF local time as taken time if the difference between file time
-                        // and EXIF local time is not less than 1 Day, otherwise MediaProvider
-                        // will use file time as taken time.
-                        time = exif.getDateTime();
-                        if (time != -1 && Math.abs(mLastModified * 1000 - time) >= 86400000) {
-                            values.put(Images.Media.DATE_TAKEN, time);
-                        }
-                    }
-
-                    int orientation = exif.getAttributeInt(
-                        ExifInterface.TAG_ORIENTATION, -1);
-                    if (orientation != -1) {
-                        // We only recognize a subset of orientation tag values.
-                        int degree;
-                        switch(orientation) {
-                            case ExifInterface.ORIENTATION_ROTATE_90:
-                                degree = 90;
-                                break;
-                            case ExifInterface.ORIENTATION_ROTATE_180:
-                                degree = 180;
-                                break;
-                            case ExifInterface.ORIENTATION_ROTATE_270:
-                                degree = 270;
-                                break;
-                            default:
-                                degree = 0;
-                                break;
-                        }
-                        values.put(Images.Media.ORIENTATION, degree);
-                    }
-                }
-            }
-
-            Uri tableUri = mFilesUri;
-            int mediaType = FileColumns.MEDIA_TYPE_NONE;
-            MediaInserter inserter = mMediaInserter;
-            if (!mNoMedia) {
-                if (MediaFile.isVideoMimeType(mMimeType)) {
-                    tableUri = mVideoUri;
-                    mediaType = FileColumns.MEDIA_TYPE_VIDEO;
-                } else if (MediaFile.isImageMimeType(mMimeType)) {
-                    tableUri = mImagesUri;
-                    mediaType = FileColumns.MEDIA_TYPE_IMAGE;
-                } else if (MediaFile.isAudioMimeType(mMimeType)) {
-                    tableUri = mAudioUri;
-                    mediaType = FileColumns.MEDIA_TYPE_AUDIO;
-                } else if (MediaFile.isPlayListMimeType(mMimeType)) {
-                    tableUri = mPlaylistsUri;
-                    mediaType = FileColumns.MEDIA_TYPE_PLAYLIST;
-                }
-            }
-            Uri result = null;
-            boolean needToSetSettings = false;
-            // Setting a flag in order not to use bulk insert for the file related with
-            // notifications, ringtones, and alarms, because the rowId of the inserted file is
-            // needed.
-            if (notifications && !mDefaultNotificationSet) {
-                if (TextUtils.isEmpty(mDefaultNotificationFilename) ||
-                        doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {
-                    needToSetSettings = true;
-                }
-            } else if (ringtones && !mDefaultRingtoneSet) {
-                if (TextUtils.isEmpty(mDefaultRingtoneFilename) ||
-                        doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) {
-                    needToSetSettings = true;
-                }
-            } else if (alarms && !mDefaultAlarmSet) {
-                if (TextUtils.isEmpty(mDefaultAlarmAlertFilename) ||
-                        doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)) {
-                    needToSetSettings = true;
-                }
-            }
-
-            if (rowId == 0) {
-                if (mMtpObjectHandle != 0) {
-                    values.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle);
-                }
-                if (tableUri == mFilesUri) {
-                    int format = entry.mFormat;
-                    if (format == 0) {
-                        format = MediaFile.getFormatCode(entry.mPath, mMimeType);
-                    }
-                    values.put(Files.FileColumns.FORMAT, format);
-                }
-                // New file, insert it.
-                // Directories need to be inserted before the files they contain, so they
-                // get priority when bulk inserting.
-                // If the rowId of the inserted file is needed, it gets inserted immediately,
-                // bypassing the bulk inserter.
-                if (inserter == null || needToSetSettings) {
-                    if (inserter != null) {
-                        inserter.flushAll();
-                    }
-                    result = mMediaProvider.insert(tableUri, values);
-                } else if (entry.mFormat == MtpConstants.FORMAT_ASSOCIATION) {
-                    inserter.insertwithPriority(tableUri, values);
-                } else {
-                    inserter.insert(tableUri, values);
-                }
-
-                if (result != null) {
-                    rowId = ContentUris.parseId(result);
-                    entry.mRowId = rowId;
-                }
-            } else {
-                // updated file
-                result = ContentUris.withAppendedId(tableUri, rowId);
-                // path should never change, and we want to avoid replacing mixed cased paths
-                // with squashed lower case paths
-                values.remove(MediaStore.MediaColumns.DATA);
-
-                if (!mNoMedia) {
-                    // Changing media type must be done as separate update
-                    if (mediaType != entry.mMediaType) {
-                        final ContentValues mediaTypeValues = new ContentValues();
-                        mediaTypeValues.put(FileColumns.MEDIA_TYPE, mediaType);
-                        mMediaProvider.update(ContentUris.withAppendedId(mFilesUri, rowId),
-                                mediaTypeValues, null, null);
-                    }
-                }
-
-                mMediaProvider.update(result, values, null, null);
-            }
-
-            if(needToSetSettings) {
-                if (notifications) {
-                    setRingtoneIfNotSet(Settings.System.NOTIFICATION_SOUND, tableUri, rowId);
-                    mDefaultNotificationSet = true;
-                } else if (ringtones) {
-                    setRingtoneIfNotSet(Settings.System.RINGTONE, tableUri, rowId);
-                    mDefaultRingtoneSet = true;
-                } else if (alarms) {
-                    setRingtoneIfNotSet(Settings.System.ALARM_ALERT, tableUri, rowId);
-                    mDefaultAlarmSet = true;
-                }
-            }
-
-            return result;
+            throw new UnsupportedOperationException();
         }
 
-        private boolean doesPathHaveFilename(String path, String filename) {
-            int pathFilenameStart = path.lastIndexOf(File.separatorChar) + 1;
-            int filenameLength = filename.length();
-            return path.regionMatches(pathFilenameStart, filename, 0, filenameLength) &&
-                    pathFilenameStart + filenameLength == path.length();
-        }
-
-        private void setRingtoneIfNotSet(String settingName, Uri uri, long rowId) {
-            if (wasRingtoneAlreadySet(settingName)) {
-                return;
-            }
-
-            ContentResolver cr = mContext.getContentResolver();
-            String existingSettingValue = Settings.System.getString(cr, settingName);
-            if (TextUtils.isEmpty(existingSettingValue)) {
-                final Uri settingUri = Settings.System.getUriFor(settingName);
-                final Uri ringtoneUri = ContentUris.withAppendedId(uri, rowId);
-                RingtoneManager.setActualDefaultRingtoneUri(mContext,
-                        RingtoneManager.getDefaultType(settingUri), ringtoneUri);
-            }
-            Settings.System.putInt(cr, settingSetIndicatorName(settingName), 1);
-        }
-
-        /** @deprecated file types no longer exist */
         @Deprecated
-        @UnsupportedAppUsage
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
         private int getFileTypeFromDrm(String path) {
-            return 0;
-        }
-
-        private void getMimeTypeFromDrm(String path) {
-            mMimeType = null;
-
-            if (mDrmManagerClient == null) {
-                mDrmManagerClient = new DrmManagerClient(mContext);
-            }
-
-            if (mDrmManagerClient.canHandle(path, null)) {
-                mIsDrm = true;
-                mMimeType = mDrmManagerClient.getOriginalMimeType(path);
-            }
-
-            if (mMimeType == null) {
-                mMimeType = ContentResolver.MIME_TYPE_DEFAULT;
-            }
-        }
-
-    }; // end of anonymous MediaScannerClient instance
-
-    private static boolean isSystemSoundWithMetadata(String path) {
-        if (path.startsWith(SYSTEM_SOUNDS_DIR + ALARMS_DIR)
-                || path.startsWith(SYSTEM_SOUNDS_DIR + RINGTONES_DIR)
-                || path.startsWith(SYSTEM_SOUNDS_DIR + NOTIFICATIONS_DIR)
-                || path.startsWith(OEM_SOUNDS_DIR + ALARMS_DIR)
-                || path.startsWith(OEM_SOUNDS_DIR + RINGTONES_DIR)
-                || path.startsWith(OEM_SOUNDS_DIR + NOTIFICATIONS_DIR)
-                || path.startsWith(PRODUCT_SOUNDS_DIR + ALARMS_DIR)
-                || path.startsWith(PRODUCT_SOUNDS_DIR + RINGTONES_DIR)
-                || path.startsWith(PRODUCT_SOUNDS_DIR + NOTIFICATIONS_DIR)) {
-            return true;
-        }
-        return false;
-    }
-
-    private String settingSetIndicatorName(String base) {
-        return base + "_set";
-    }
-
-    private boolean wasRingtoneAlreadySet(String name) {
-        ContentResolver cr = mContext.getContentResolver();
-        String indicatorName = settingSetIndicatorName(name);
-        try {
-            return Settings.System.getInt(cr, indicatorName) != 0;
-        } catch (SettingNotFoundException e) {
-            return false;
+            throw new UnsupportedOperationException();
         }
     }
 
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     private void prescan(String filePath, boolean prescanFiles) throws RemoteException {
-        Cursor c = null;
-        String where = null;
-        String[] selectionArgs = null;
-
-        mPlayLists.clear();
-
-        if (filePath != null) {
-            // query for only one file
-            where = MediaStore.Files.FileColumns._ID + ">?" +
-                " AND " + Files.FileColumns.DATA + "=?";
-            selectionArgs = new String[] { "", filePath };
-        } else {
-            where = MediaStore.Files.FileColumns._ID + ">?";
-            selectionArgs = new String[] { "" };
-        }
-
-        mDefaultRingtoneSet = wasRingtoneAlreadySet(Settings.System.RINGTONE);
-        mDefaultNotificationSet = wasRingtoneAlreadySet(Settings.System.NOTIFICATION_SOUND);
-        mDefaultAlarmSet = wasRingtoneAlreadySet(Settings.System.ALARM_ALERT);
-
-        // Tell the provider to not delete the file.
-        // If the file is truly gone the delete is unnecessary, and we want to avoid
-        // accidentally deleting files that are really there (this may happen if the
-        // filesystem is mounted and unmounted while the scanner is running).
-        Uri.Builder builder = mFilesUri.buildUpon();
-        builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false");
-        MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, builder.build());
-
-        // Build the list of files from the content provider
-        try {
-            if (prescanFiles) {
-                // First read existing files from the files table.
-                // Because we'll be deleting entries for missing files as we go,
-                // we need to query the database in small batches, to avoid problems
-                // with CursorWindow positioning.
-                long lastId = Long.MIN_VALUE;
-                Uri limitUri = mFilesUri.buildUpon()
-                        .appendQueryParameter(MediaStore.PARAM_LIMIT, "1000").build();
-
-                while (true) {
-                    selectionArgs[0] = "" + lastId;
-                    if (c != null) {
-                        c.close();
-                        c = null;
-                    }
-                    c = mMediaProvider.query(limitUri, FILES_PRESCAN_PROJECTION,
-                            where, selectionArgs, MediaStore.Files.FileColumns._ID, null);
-                    if (c == null) {
-                        break;
-                    }
-
-                    int num = c.getCount();
-
-                    if (num == 0) {
-                        break;
-                    }
-                    while (c.moveToNext()) {
-                        long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
-                        String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
-                        int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
-                        long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
-                        lastId = rowId;
-
-                        // Only consider entries with absolute path names.
-                        // This allows storing URIs in the database without the
-                        // media scanner removing them.
-                        if (path != null && path.startsWith("/")) {
-                            boolean exists = false;
-                            try {
-                                exists = Os.access(path, android.system.OsConstants.F_OK);
-                            } catch (ErrnoException e1) {
-                            }
-                            if (!exists && !MtpConstants.isAbstractObject(format)) {
-                                // do not delete missing playlists, since they may have been
-                                // modified by the user.
-                                // The user can delete them in the media player instead.
-                                // instead, clear the path and lastModified fields in the row
-                                String mimeType = MediaFile.getMimeTypeForFile(path);
-                                if (!MediaFile.isPlayListMimeType(mimeType)) {
-                                    deleter.delete(rowId);
-                                    if (path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
-                                        deleter.flush();
-                                        String parent = new File(path).getParent();
-                                        mMediaProvider.call(MediaStore.UNHIDE_CALL, parent, null);
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        finally {
-            if (c != null) {
-                c.close();
-            }
-            deleter.flush();
-        }
-
-        // compute original size of images
-        mOriginalCount = 0;
-        c = mMediaProvider.query(mImagesUri, ID_PROJECTION, null, null, null, null);
-        if (c != null) {
-            mOriginalCount = c.getCount();
-            c.close();
-        }
+        throw new UnsupportedOperationException();
     }
 
-    static class MediaBulkDeleter {
-        StringBuilder whereClause = new StringBuilder();
-        ArrayList<String> whereArgs = new ArrayList<String>(100);
-        final ContentProviderClient mProvider;
-        final Uri mBaseUri;
-
-        public MediaBulkDeleter(ContentProviderClient provider, Uri baseUri) {
-            mProvider = provider;
-            mBaseUri = baseUri;
-        }
-
-        public void delete(long id) throws RemoteException {
-            if (whereClause.length() != 0) {
-                whereClause.append(",");
-            }
-            whereClause.append("?");
-            whereArgs.add("" + id);
-            if (whereArgs.size() > 100) {
-                flush();
-            }
-        }
-        public void flush() throws RemoteException {
-            int size = whereArgs.size();
-            if (size > 0) {
-                String [] foo = new String [size];
-                foo = whereArgs.toArray(foo);
-                int numrows = mProvider.delete(mBaseUri,
-                        MediaStore.MediaColumns._ID + " IN (" +
-                        whereClause.toString() + ")", foo);
-                //Log.i("@@@@@@@@@", "rows deleted: " + numrows);
-                whereClause.setLength(0);
-                whereArgs.clear();
-            }
-        }
-    }
-
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     private void postscan(final String[] directories) throws RemoteException {
-
-        // handle playlists last, after we know what media files are on the storage.
-        if (mProcessPlaylists) {
-            processPlayLists();
-        }
-
-        // allow GC to clean up
-        mPlayLists.clear();
+        throw new UnsupportedOperationException();
     }
 
-    private void releaseResources() {
-        // release the DrmManagerClient resources
-        if (mDrmManagerClient != null) {
-            mDrmManagerClient.close();
-            mDrmManagerClient = null;
-        }
-    }
-
-    public void scanDirectories(String[] directories) {
-        try {
-            long start = System.currentTimeMillis();
-            prescan(null, true);
-            long prescan = System.currentTimeMillis();
-
-            if (ENABLE_BULK_INSERTS) {
-                // create MediaInserter for bulk inserts
-                mMediaInserter = new MediaInserter(mMediaProvider, 500);
-            }
-
-            for (int i = 0; i < directories.length; i++) {
-                processDirectory(directories[i], mClient);
-            }
-
-            if (ENABLE_BULK_INSERTS) {
-                // flush remaining inserts
-                mMediaInserter.flushAll();
-                mMediaInserter = null;
-            }
-
-            long scan = System.currentTimeMillis();
-            postscan(directories);
-            long end = System.currentTimeMillis();
-
-            if (false) {
-                Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n");
-                Log.d(TAG, "    scan time: " + (scan - prescan) + "ms\n");
-                Log.d(TAG, "postscan time: " + (end - scan) + "ms\n");
-                Log.d(TAG, "   total time: " + (end - start) + "ms\n");
-            }
-        } catch (SQLException e) {
-            // this might happen if the SD card is removed while the media scanner is running
-            Log.e(TAG, "SQLException in MediaScanner.scan()", e);
-        } catch (UnsupportedOperationException e) {
-            // this might happen if the SD card is removed while the media scanner is running
-            Log.e(TAG, "UnsupportedOperationException in MediaScanner.scan()", e);
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in MediaScanner.scan()", e);
-        } finally {
-            releaseResources();
-        }
-    }
-
-    // this function is used to scan a single file
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     public Uri scanSingleFile(String path, String mimeType) {
-        try {
-            prescan(path, true);
-
-            File file = new File(path);
-            if (!file.exists() || !file.canRead()) {
-                return null;
-            }
-
-            // lastModified is in milliseconds on Files.
-            long lastModifiedSeconds = file.lastModified() / 1000;
-
-            // always scan the file, so we can return the content://media Uri for existing files
-            return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),
-                    false, true, MediaScanner.isNoMediaPath(path));
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
-            return null;
-        } finally {
-            releaseResources();
-        }
+        throw new UnsupportedOperationException();
     }
 
-    private static boolean isNoMediaFile(String path) {
-        File file = new File(path);
-        if (file.isDirectory()) return false;
-
-        // special case certain file names
-        // I use regionMatches() instead of substring() below
-        // to avoid memory allocation
-        int lastSlash = path.lastIndexOf('/');
-        if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
-            // ignore those ._* files created by MacOS
-            if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
-                return true;
-            }
-
-            // ignore album art files created by Windows Media Player:
-            // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg
-            // and AlbumArt_{...}_Small.jpg
-            if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) {
-                if (path.regionMatches(true, lastSlash + 1, "AlbumArt_{", 0, 10) ||
-                        path.regionMatches(true, lastSlash + 1, "AlbumArt.", 0, 9)) {
-                    return true;
-                }
-                int length = path.length() - lastSlash - 1;
-                if ((length == 17 && path.regionMatches(
-                        true, lastSlash + 1, "AlbumArtSmall", 0, 13)) ||
-                        (length == 10
-                         && path.regionMatches(true, lastSlash + 1, "Folder", 0, 6))) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private static HashMap<String,String> mNoMediaPaths = new HashMap<String,String>();
-    private static HashMap<String,String> mMediaPaths = new HashMap<String,String>();
-
-    /* MediaProvider calls this when a .nomedia file is added or removed */
-    public static void clearMediaPathCache(boolean clearMediaPaths, boolean clearNoMediaPaths) {
-        synchronized (MediaScanner.class) {
-            if (clearMediaPaths) {
-                mMediaPaths.clear();
-            }
-            if (clearNoMediaPaths) {
-                mNoMediaPaths.clear();
-            }
-        }
-    }
-
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     public static boolean isNoMediaPath(String path) {
-        if (path == null) {
-            return false;
-        }
-        // return true if file or any parent directory has name starting with a dot
-        if (path.indexOf("/.") >= 0) {
-            return true;
-        }
-
-        int firstSlash = path.lastIndexOf('/');
-        if (firstSlash <= 0) {
-            return false;
-        }
-        String parent = path.substring(0,  firstSlash);
-
-        synchronized (MediaScanner.class) {
-            if (mNoMediaPaths.containsKey(parent)) {
-                return true;
-            } else if (!mMediaPaths.containsKey(parent)) {
-                // check to see if any parent directories have a ".nomedia" file
-                // start from 1 so we don't bother checking in the root directory
-                int offset = 1;
-                while (offset >= 0) {
-                    int slashIndex = path.indexOf('/', offset);
-                    if (slashIndex > offset) {
-                        slashIndex++; // move past slash
-                        File file = new File(path.substring(0, slashIndex) + ".nomedia");
-                        if (file.exists()) {
-                            // we have a .nomedia in one of the parent directories
-                            mNoMediaPaths.put(parent, "");
-                            return true;
-                        }
-                    }
-                    offset = slashIndex;
-                }
-                mMediaPaths.put(parent, "");
-            }
-        }
-
-        return isNoMediaFile(path);
+        throw new UnsupportedOperationException();
     }
 
-    public void scanMtpFile(String path, int objectHandle, int format) {
-        String mimeType = MediaFile.getMimeType(path, format);
-        File file = new File(path);
-        long lastModifiedSeconds = file.lastModified() / 1000;
-
-        if (!MediaFile.isAudioMimeType(mimeType) && !MediaFile.isVideoMimeType(mimeType) &&
-            !MediaFile.isImageMimeType(mimeType) && !MediaFile.isPlayListMimeType(mimeType) &&
-            !MediaFile.isDrmMimeType(mimeType)) {
-
-            // no need to use the media scanner, but we need to update last modified and file size
-            ContentValues values = new ContentValues();
-            values.put(Files.FileColumns.SIZE, file.length());
-            values.put(Files.FileColumns.DATE_MODIFIED, lastModifiedSeconds);
-            try {
-                String[] whereArgs = new String[] {  Integer.toString(objectHandle) };
-                mMediaProvider.update(Files.getMtpObjectsUri(mVolumeName), values,
-                        "_id=?", whereArgs);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException in scanMtpFile", e);
-            }
-            return;
-        }
-
-        mMtpObjectHandle = objectHandle;
-        Cursor fileList = null;
-        try {
-            if (MediaFile.isPlayListMimeType(mimeType)) {
-                // build file cache so we can look up tracks in the playlist
-                prescan(null, true);
-
-                FileEntry entry = makeEntryFor(path);
-                if (entry != null) {
-                    fileList = mMediaProvider.query(mFilesUri,
-                            FILES_PRESCAN_PROJECTION, null, null, null, null);
-                    processPlayList(entry, fileList);
-                }
-            } else {
-                // MTP will create a file entry for us so we don't want to do it in prescan
-                prescan(path, false);
-
-                // always scan the file, so we can return the content://media Uri for existing files
-                mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),
-                    (format == MtpConstants.FORMAT_ASSOCIATION), true, isNoMediaPath(path));
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
-        } finally {
-            mMtpObjectHandle = 0;
-            if (fileList != null) {
-                fileList.close();
-            }
-            releaseResources();
-        }
-    }
-
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
     FileEntry makeEntryFor(String path) {
-        String where;
-        String[] selectionArgs;
-
-        Cursor c = null;
-        try {
-            where = Files.FileColumns.DATA + "=?";
-            selectionArgs = new String[] { path };
-            c = mMediaProvider.query(mFilesFullUri, FILES_PRESCAN_PROJECTION,
-                    where, selectionArgs, null, null);
-            if (c != null && c.moveToFirst()) {
-                long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
-                long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
-                int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
-                int mediaType = c.getInt(FILES_PRESCAN_MEDIA_TYPE_COLUMN_INDEX);
-                return new FileEntry(rowId, path, lastModified, format, mediaType);
-            }
-        } catch (RemoteException e) {
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-        return null;
+        throw new UnsupportedOperationException();
     }
 
-    // returns the number of matching file/directory names, starting from the right
-    private int matchPaths(String path1, String path2) {
-        int result = 0;
-        int end1 = path1.length();
-        int end2 = path2.length();
-
-        while (end1 > 0 && end2 > 0) {
-            int slash1 = path1.lastIndexOf('/', end1 - 1);
-            int slash2 = path2.lastIndexOf('/', end2 - 1);
-            int backSlash1 = path1.lastIndexOf('\\', end1 - 1);
-            int backSlash2 = path2.lastIndexOf('\\', end2 - 1);
-            int start1 = (slash1 > backSlash1 ? slash1 : backSlash1);
-            int start2 = (slash2 > backSlash2 ? slash2 : backSlash2);
-            if (start1 < 0) start1 = 0; else start1++;
-            if (start2 < 0) start2 = 0; else start2++;
-            int length = end1 - start1;
-            if (end2 - start2 != length) break;
-            if (path1.regionMatches(true, start1, path2, start2, length)) {
-                result++;
-                end1 = start1 - 1;
-                end2 = start2 - 1;
-            } else break;
-        }
-
-        return result;
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
+    private void setLocale(String locale) {
+        throw new UnsupportedOperationException();
     }
 
-    private boolean matchEntries(long rowId, String data) {
-
-        int len = mPlaylistEntries.size();
-        boolean done = true;
-        for (int i = 0; i < len; i++) {
-            PlaylistEntry entry = mPlaylistEntries.get(i);
-            if (entry.bestmatchlevel == Integer.MAX_VALUE) {
-                continue; // this entry has been matched already
-            }
-            done = false;
-            if (data.equalsIgnoreCase(entry.path)) {
-                entry.bestmatchid = rowId;
-                entry.bestmatchlevel = Integer.MAX_VALUE;
-                continue; // no need for path matching
-            }
-
-            int matchLength = matchPaths(data, entry.path);
-            if (matchLength > entry.bestmatchlevel) {
-                entry.bestmatchid = rowId;
-                entry.bestmatchlevel = matchLength;
-            }
-        }
-        return done;
-    }
-
-    private void cachePlaylistEntry(String line, String playListDirectory) {
-        PlaylistEntry entry = new PlaylistEntry();
-        // watch for trailing whitespace
-        int entryLength = line.length();
-        while (entryLength > 0 && Character.isWhitespace(line.charAt(entryLength - 1))) entryLength--;
-        // path should be longer than 3 characters.
-        // avoid index out of bounds errors below by returning here.
-        if (entryLength < 3) return;
-        if (entryLength < line.length()) line = line.substring(0, entryLength);
-
-        // does entry appear to be an absolute path?
-        // look for Unix or DOS absolute paths
-        char ch1 = line.charAt(0);
-        boolean fullPath = (ch1 == '/' ||
-                (Character.isLetter(ch1) && line.charAt(1) == ':' && line.charAt(2) == '\\'));
-        // if we have a relative path, combine entry with playListDirectory
-        if (!fullPath)
-            line = playListDirectory + line;
-        entry.path = line;
-        //FIXME - should we look for "../" within the path?
-
-        mPlaylistEntries.add(entry);
-    }
-
-    private void processCachedPlaylist(Cursor fileList, ContentValues values, Uri playlistUri) {
-        fileList.moveToPosition(-1);
-        while (fileList.moveToNext()) {
-            long rowId = fileList.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
-            String data = fileList.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
-            if (matchEntries(rowId, data)) {
-                break;
-            }
-        }
-
-        int len = mPlaylistEntries.size();
-        int index = 0;
-        for (int i = 0; i < len; i++) {
-            PlaylistEntry entry = mPlaylistEntries.get(i);
-            if (entry.bestmatchlevel > 0) {
-                try {
-                    values.clear();
-                    values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(index));
-                    values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, Long.valueOf(entry.bestmatchid));
-                    mMediaProvider.insert(playlistUri, values);
-                    index++;
-                } catch (RemoteException e) {
-                    Log.e(TAG, "RemoteException in MediaScanner.processCachedPlaylist()", e);
-                    return;
-                }
-            }
-        }
-        mPlaylistEntries.clear();
-    }
-
-    private void processM3uPlayList(String path, String playListDirectory, Uri uri,
-            ContentValues values, Cursor fileList) {
-        BufferedReader reader = null;
-        try {
-            File f = new File(path);
-            if (f.exists()) {
-                reader = new BufferedReader(
-                        new InputStreamReader(new FileInputStream(f)), 8192);
-                String line = reader.readLine();
-                mPlaylistEntries.clear();
-                while (line != null) {
-                    // ignore comment lines, which begin with '#'
-                    if (line.length() > 0 && line.charAt(0) != '#') {
-                        cachePlaylistEntry(line, playListDirectory);
-                    }
-                    line = reader.readLine();
-                }
-
-                processCachedPlaylist(fileList, values, uri);
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "IOException in MediaScanner.processM3uPlayList()", e);
-        } finally {
-            try {
-                if (reader != null)
-                    reader.close();
-            } catch (IOException e) {
-                Log.e(TAG, "IOException in MediaScanner.processM3uPlayList()", e);
-            }
-        }
-    }
-
-    private void processPlsPlayList(String path, String playListDirectory, Uri uri,
-            ContentValues values, Cursor fileList) {
-        BufferedReader reader = null;
-        try {
-            File f = new File(path);
-            if (f.exists()) {
-                reader = new BufferedReader(
-                        new InputStreamReader(new FileInputStream(f)), 8192);
-                String line = reader.readLine();
-                mPlaylistEntries.clear();
-                while (line != null) {
-                    // ignore comment lines, which begin with '#'
-                    if (line.startsWith("File")) {
-                        int equals = line.indexOf('=');
-                        if (equals > 0) {
-                            cachePlaylistEntry(line.substring(equals + 1), playListDirectory);
-                        }
-                    }
-                    line = reader.readLine();
-                }
-
-                processCachedPlaylist(fileList, values, uri);
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "IOException in MediaScanner.processPlsPlayList()", e);
-        } finally {
-            try {
-                if (reader != null)
-                    reader.close();
-            } catch (IOException e) {
-                Log.e(TAG, "IOException in MediaScanner.processPlsPlayList()", e);
-            }
-        }
-    }
-
-    class WplHandler implements ElementListener {
-
-        final ContentHandler handler;
-        String playListDirectory;
-
-        public WplHandler(String playListDirectory, Uri uri, Cursor fileList) {
-            this.playListDirectory = playListDirectory;
-
-            RootElement root = new RootElement("smil");
-            Element body = root.getChild("body");
-            Element seq = body.getChild("seq");
-            Element media = seq.getChild("media");
-            media.setElementListener(this);
-
-            this.handler = root.getContentHandler();
-        }
-
-        @Override
-        public void start(Attributes attributes) {
-            String path = attributes.getValue("", "src");
-            if (path != null) {
-                cachePlaylistEntry(path, playListDirectory);
-            }
-        }
-
-       @Override
-       public void end() {
-       }
-
-        ContentHandler getContentHandler() {
-            return handler;
-        }
-    }
-
-    private void processWplPlayList(String path, String playListDirectory, Uri uri,
-            ContentValues values, Cursor fileList) {
-        FileInputStream fis = null;
-        try {
-            File f = new File(path);
-            if (f.exists()) {
-                fis = new FileInputStream(f);
-
-                mPlaylistEntries.clear();
-                Xml.parse(fis, Xml.findEncodingByName("UTF-8"),
-                        new WplHandler(playListDirectory, uri, fileList).getContentHandler());
-
-                processCachedPlaylist(fileList, values, uri);
-            }
-        } catch (SAXException e) {
-            e.printStackTrace();
-        } catch (IOException e) {
-            e.printStackTrace();
-        } finally {
-            try {
-                if (fis != null)
-                    fis.close();
-            } catch (IOException e) {
-                Log.e(TAG, "IOException in MediaScanner.processWplPlayList()", e);
-            }
-        }
-    }
-
-    private void processPlayList(FileEntry entry, Cursor fileList) throws RemoteException {
-        String path = entry.mPath;
-        ContentValues values = new ContentValues();
-        int lastSlash = path.lastIndexOf('/');
-        if (lastSlash < 0) throw new IllegalArgumentException("bad path " + path);
-        Uri uri, membersUri;
-        long rowId = entry.mRowId;
-
-        // make sure we have a name
-        String name = values.getAsString(MediaStore.Audio.Playlists.NAME);
-        if (name == null) {
-            name = values.getAsString(MediaStore.MediaColumns.TITLE);
-            if (name == null) {
-                // extract name from file name
-                int lastDot = path.lastIndexOf('.');
-                name = (lastDot < 0 ? path.substring(lastSlash + 1)
-                        : path.substring(lastSlash + 1, lastDot));
-            }
-        }
-
-        values.put(MediaStore.Audio.Playlists.NAME, name);
-        values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified);
-
-        if (rowId == 0) {
-            values.put(MediaStore.Audio.Playlists.DATA, path);
-            uri = mMediaProvider.insert(mPlaylistsUri, values);
-            rowId = ContentUris.parseId(uri);
-            membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
-        } else {
-            uri = ContentUris.withAppendedId(mPlaylistsUri, rowId);
-            mMediaProvider.update(uri, values, null, null);
-
-            // delete members of existing playlist
-            membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
-            mMediaProvider.delete(membersUri, null, null);
-        }
-
-        String playListDirectory = path.substring(0, lastSlash + 1);
-        String mimeType = MediaFile.getMimeTypeForFile(path);
-        switch (mimeType) {
-            case "application/vnd.ms-wpl":
-                processWplPlayList(path, playListDirectory, membersUri, values, fileList);
-                break;
-            case "audio/x-mpegurl":
-                processM3uPlayList(path, playListDirectory, membersUri, values, fileList);
-                break;
-            case "audio/x-scpls":
-                processPlsPlayList(path, playListDirectory, membersUri, values, fileList);
-                break;
-        }
-    }
-
-    private void processPlayLists() throws RemoteException {
-        Iterator<FileEntry> iterator = mPlayLists.iterator();
-        Cursor fileList = null;
-        try {
-            // use the files uri and projection because we need the format column,
-            // but restrict the query to just audio files
-            fileList = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
-                    "media_type=2", null, null, null);
-            while (iterator.hasNext()) {
-                FileEntry entry = iterator.next();
-                // only process playlist files if they are new or have been modified since the last scan
-                if (entry.mLastModifiedChanged) {
-                    processPlayList(entry, fileList);
-                }
-            }
-        } catch (RemoteException e1) {
-        } finally {
-            if (fileList != null) {
-                fileList.close();
-            }
-        }
-    }
-
-    private native void processDirectory(String path, MediaScannerClient client);
-    private native boolean processFile(String path, String mimeType, MediaScannerClient client);
-    @UnsupportedAppUsage
-    private native void setLocale(String locale);
-
-    public native byte[] extractAlbumArt(FileDescriptor fd);
-
-    private static native final void native_init();
-    private native final void native_setup();
-    private native final void native_finalize();
-
     @Override
     public void close() {
-        mCloseGuard.close();
-        if (mClosed.compareAndSet(false, true)) {
-            mMediaProvider.close();
-            native_finalize();
-        }
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            if (mCloseGuard != null) {
-                mCloseGuard.warnIfOpen();
-            }
-
-            close();
-        } finally {
-            super.finalize();
-        }
+        throw new UnsupportedOperationException();
     }
 }
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 435d8d7..ff40442 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -846,7 +846,7 @@
      * Adds an audio file to the list of ringtones.
      *
      * After making sure the given file is an audio file, copies the file to the ringtone storage,
-     * and asks the {@link android.media.MediaScanner} to scan that file. This call will block until
+     * and asks the system to scan that file. This call will block until
      * the scan is completed.
      *
      * The directory where the copied file is stored is the directory that matches the ringtone's
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index fb581b5..a315c1e 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -139,6 +139,12 @@
 
     /**
      * Create a thumbnail for given audio file.
+     * <p>
+     * This method should only be used for files that you have direct access to;
+     * if you'd like to work with media hosted outside your app, consider using
+     * {@link ContentResolver#loadThumbnail(Uri, Size, CancellationSignal)}
+     * which enables remote providers to efficiently cache and invalidate
+     * thumbnails.
      *
      * @param file The audio file.
      * @param size The desired thumbnail size.
@@ -231,6 +237,12 @@
 
     /**
      * Create a thumbnail for given image file.
+     * <p>
+     * This method should only be used for files that you have direct access to;
+     * if you'd like to work with media hosted outside your app, consider using
+     * {@link ContentResolver#loadThumbnail(Uri, Size, CancellationSignal)}
+     * which enables remote providers to efficiently cache and invalidate
+     * thumbnails.
      *
      * @param file The audio file.
      * @param size The desired thumbnail size.
@@ -334,6 +346,12 @@
 
     /**
      * Create a thumbnail for given video file.
+     * <p>
+     * This method should only be used for files that you have direct access to;
+     * if you'd like to work with media hosted outside your app, consider using
+     * {@link ContentResolver#loadThumbnail(Uri, Size, CancellationSignal)}
+     * which enables remote providers to efficiently cache and invalidate
+     * thumbnails.
      *
      * @param file The video file.
      * @param size The desired thumbnail size.
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 45ee210..bd9ea13 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -17,7 +17,6 @@
         "android_media_MediaPlayer.cpp",
         "android_media_MediaProfiles.cpp",
         "android_media_MediaRecorder.cpp",
-        "android_media_MediaScanner.cpp",
         "android_media_MediaSync.cpp",
         "android_media_ResampleInputStream.cpp",
         "android_media_Streams.cpp",
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index d24edc7..94299bc 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1446,7 +1446,6 @@
 extern int register_android_media_MediaMetadataRetriever(JNIEnv *env);
 extern int register_android_media_MediaMuxer(JNIEnv *env);
 extern int register_android_media_MediaRecorder(JNIEnv *env);
-extern int register_android_media_MediaScanner(JNIEnv *env);
 extern int register_android_media_MediaSync(JNIEnv *env);
 extern int register_android_media_ResampleInputStream(JNIEnv *env);
 extern int register_android_media_MediaProfiles(JNIEnv *env);
@@ -1485,11 +1484,6 @@
         goto bail;
     }
 
-    if (register_android_media_MediaScanner(env) < 0) {
-        ALOGE("ERROR: MediaScanner native registration failed\n");
-        goto bail;
-    }
-
     if (register_android_media_MediaMetadataRetriever(env) < 0) {
         ALOGE("ERROR: MediaMetadataRetriever native registration failed\n");
         goto bail;
diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp
deleted file mode 100644
index 58044c0..0000000
--- a/media/jni/android_media_MediaScanner.cpp
+++ /dev/null
@@ -1,468 +0,0 @@
-/*
-**
-** Copyright 2007, 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.
-*/
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "MediaScannerJNI"
-#include <utils/Log.h>
-#include <utils/threads.h>
-#include <media/mediascanner.h>
-#include <media/stagefright/StagefrightMediaScanner.h>
-#include <private/media/VideoFrame.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
-#include <android-base/macros.h>                // for FALLTHROUGH_INTENDED
-
-using namespace android;
-
-
-static const char* const kClassMediaScannerClient =
-        "android/media/MediaScannerClient";
-
-static const char* const kClassMediaScanner =
-        "android/media/MediaScanner";
-
-static const char* const kRunTimeException =
-        "java/lang/RuntimeException";
-
-static const char* const kIllegalArgumentException =
-        "java/lang/IllegalArgumentException";
-
-struct fields_t {
-    jfieldID    context;
-};
-static fields_t fields;
-
-static status_t checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
-    if (env->ExceptionCheck()) {
-        ALOGE("An exception was thrown by callback '%s'.", methodName);
-        LOGE_EX(env);
-        env->ExceptionClear();
-        return UNKNOWN_ERROR;
-    }
-    return OK;
-}
-
-// stolen from dalvik/vm/checkJni.cpp
-static bool isValidUtf8(const char* bytes) {
-    while (*bytes != '\0') {
-        unsigned char utf8 = *(bytes++);
-        // Switch on the high four bits.
-        switch (utf8 >> 4) {
-        case 0x00:
-        case 0x01:
-        case 0x02:
-        case 0x03:
-        case 0x04:
-        case 0x05:
-        case 0x06:
-        case 0x07:
-            // Bit pattern 0xxx. No need for any extra bytes.
-            break;
-        case 0x08:
-        case 0x09:
-        case 0x0a:
-        case 0x0b:
-        case 0x0f:
-            /*
-             * Bit pattern 10xx or 1111, which are illegal start bytes.
-             * Note: 1111 is valid for normal UTF-8, but not the
-             * modified UTF-8 used here.
-             */
-            return false;
-        case 0x0e:
-            // Bit pattern 1110, so there are two additional bytes.
-            utf8 = *(bytes++);
-            if ((utf8 & 0xc0) != 0x80) {
-                return false;
-            }
-            // Fall through to take care of the final byte.
-            FALLTHROUGH_INTENDED;
-        case 0x0c:
-        case 0x0d:
-            // Bit pattern 110x, so there is one additional byte.
-            utf8 = *(bytes++);
-            if ((utf8 & 0xc0) != 0x80) {
-                return false;
-            }
-            break;
-        }
-    }
-    return true;
-}
-
-class MyMediaScannerClient : public MediaScannerClient
-{
-public:
-    MyMediaScannerClient(JNIEnv *env, jobject client)
-        :   mEnv(env),
-            mClient(env->NewGlobalRef(client)),
-            mScanFileMethodID(0),
-            mHandleStringTagMethodID(0),
-            mSetMimeTypeMethodID(0)
-    {
-        ALOGV("MyMediaScannerClient constructor");
-        jclass mediaScannerClientInterface =
-                env->FindClass(kClassMediaScannerClient);
-
-        if (mediaScannerClientInterface == NULL) {
-            ALOGE("Class %s not found", kClassMediaScannerClient);
-        } else {
-            mScanFileMethodID = env->GetMethodID(
-                                    mediaScannerClientInterface,
-                                    "scanFile",
-                                    "(Ljava/lang/String;JJZZ)V");
-
-            mHandleStringTagMethodID = env->GetMethodID(
-                                    mediaScannerClientInterface,
-                                    "handleStringTag",
-                                    "(Ljava/lang/String;Ljava/lang/String;)V");
-
-            mSetMimeTypeMethodID = env->GetMethodID(
-                                    mediaScannerClientInterface,
-                                    "setMimeType",
-                                    "(Ljava/lang/String;)V");
-        }
-    }
-
-    virtual ~MyMediaScannerClient()
-    {
-        ALOGV("MyMediaScannerClient destructor");
-        mEnv->DeleteGlobalRef(mClient);
-    }
-
-    virtual status_t scanFile(const char* path, long long lastModified,
-            long long fileSize, bool isDirectory, bool noMedia)
-    {
-        ALOGV("scanFile: path(%s), time(%lld), size(%lld) and isDir(%d)",
-            path, lastModified, fileSize, isDirectory);
-
-        jstring pathStr;
-        if ((pathStr = mEnv->NewStringUTF(path)) == NULL) {
-            mEnv->ExceptionClear();
-            return NO_MEMORY;
-        }
-
-        mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
-                fileSize, isDirectory, noMedia);
-
-        mEnv->DeleteLocalRef(pathStr);
-        return checkAndClearExceptionFromCallback(mEnv, "scanFile");
-    }
-
-    virtual status_t handleStringTag(const char* name, const char* value)
-    {
-        ALOGV("handleStringTag: name(%s) and value(%s)", name, value);
-        jstring nameStr, valueStr;
-        if ((nameStr = mEnv->NewStringUTF(name)) == NULL) {
-            mEnv->ExceptionClear();
-            return NO_MEMORY;
-        }
-        char *cleaned = NULL;
-        if (!isValidUtf8(value)) {
-            cleaned = strdup(value);
-            char *chp = cleaned;
-            char ch;
-            while ((ch = *chp)) {
-                if (ch & 0x80) {
-                    *chp = '?';
-                }
-                chp++;
-            }
-            value = cleaned;
-        }
-        valueStr = mEnv->NewStringUTF(value);
-        free(cleaned);
-        if (valueStr == NULL) {
-            mEnv->DeleteLocalRef(nameStr);
-            mEnv->ExceptionClear();
-            return NO_MEMORY;
-        }
-
-        mEnv->CallVoidMethod(
-            mClient, mHandleStringTagMethodID, nameStr, valueStr);
-
-        mEnv->DeleteLocalRef(nameStr);
-        mEnv->DeleteLocalRef(valueStr);
-        return checkAndClearExceptionFromCallback(mEnv, "handleStringTag");
-    }
-
-    virtual status_t setMimeType(const char* mimeType)
-    {
-        ALOGV("setMimeType: %s", mimeType);
-        jstring mimeTypeStr;
-        if ((mimeTypeStr = mEnv->NewStringUTF(mimeType)) == NULL) {
-            mEnv->ExceptionClear();
-            return NO_MEMORY;
-        }
-
-        mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr);
-
-        mEnv->DeleteLocalRef(mimeTypeStr);
-        return checkAndClearExceptionFromCallback(mEnv, "setMimeType");
-    }
-
-private:
-    JNIEnv *mEnv;
-    jobject mClient;
-    jmethodID mScanFileMethodID;
-    jmethodID mHandleStringTagMethodID;
-    jmethodID mSetMimeTypeMethodID;
-};
-
-
-static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz)
-{
-    return (MediaScanner *) env->GetLongField(thiz, fields.context);
-}
-
-static void setNativeScanner_l(JNIEnv* env, jobject thiz, MediaScanner *s)
-{
-    env->SetLongField(thiz, fields.context, (jlong)s);
-}
-
-static void
-android_media_MediaScanner_processDirectory(
-        JNIEnv *env, jobject thiz, jstring path, jobject client)
-{
-    ALOGV("processDirectory");
-    MediaScanner *mp = getNativeScanner_l(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, kRunTimeException, "No scanner available");
-        return;
-    }
-
-    if (path == NULL) {
-        jniThrowException(env, kIllegalArgumentException, NULL);
-        return;
-    }
-
-    const char *pathStr = env->GetStringUTFChars(path, NULL);
-    if (pathStr == NULL) {  // Out of memory
-        return;
-    }
-
-    MyMediaScannerClient myClient(env, client);
-    MediaScanResult result = mp->processDirectory(pathStr, myClient);
-    if (result == MEDIA_SCAN_RESULT_ERROR) {
-        ALOGE("An error occurred while scanning directory '%s'.", pathStr);
-    }
-    env->ReleaseStringUTFChars(path, pathStr);
-}
-
-static jboolean
-android_media_MediaScanner_processFile(
-        JNIEnv *env, jobject thiz, jstring path,
-        jstring mimeType, jobject client)
-{
-    ALOGV("processFile");
-
-    // Lock already hold by processDirectory
-    MediaScanner *mp = getNativeScanner_l(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, kRunTimeException, "No scanner available");
-        return false;
-    }
-
-    if (path == NULL) {
-        jniThrowException(env, kIllegalArgumentException, NULL);
-        return false;
-    }
-
-    const char *pathStr = env->GetStringUTFChars(path, NULL);
-    if (pathStr == NULL) {  // Out of memory
-        return false;
-    }
-
-    const char *mimeTypeStr =
-        (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
-    if (mimeType && mimeTypeStr == NULL) {  // Out of memory
-        // ReleaseStringUTFChars can be called with an exception pending.
-        env->ReleaseStringUTFChars(path, pathStr);
-        return false;
-    }
-
-    MyMediaScannerClient myClient(env, client);
-    MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient);
-    if (result == MEDIA_SCAN_RESULT_ERROR) {
-        ALOGE("An error occurred while scanning file '%s'.", pathStr);
-    }
-    env->ReleaseStringUTFChars(path, pathStr);
-    if (mimeType) {
-        env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
-    }
-    return result != MEDIA_SCAN_RESULT_ERROR;
-}
-
-static void
-android_media_MediaScanner_setLocale(
-        JNIEnv *env, jobject thiz, jstring locale)
-{
-    ALOGV("setLocale");
-    MediaScanner *mp = getNativeScanner_l(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, kRunTimeException, "No scanner available");
-        return;
-    }
-
-    if (locale == NULL) {
-        jniThrowException(env, kIllegalArgumentException, NULL);
-        return;
-    }
-    const char *localeStr = env->GetStringUTFChars(locale, NULL);
-    if (localeStr == NULL) {  // Out of memory
-        return;
-    }
-    mp->setLocale(localeStr);
-
-    env->ReleaseStringUTFChars(locale, localeStr);
-}
-
-static jbyteArray
-android_media_MediaScanner_extractAlbumArt(
-        JNIEnv *env, jobject thiz, jobject fileDescriptor)
-{
-    ALOGV("extractAlbumArt");
-    MediaScanner *mp = getNativeScanner_l(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, kRunTimeException, "No scanner available");
-        return NULL;
-    }
-
-    if (fileDescriptor == NULL) {
-        jniThrowException(env, kIllegalArgumentException, NULL);
-        return NULL;
-    }
-
-    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-    MediaAlbumArt* mediaAlbumArt = mp->extractAlbumArt(fd);
-    if (mediaAlbumArt == NULL) {
-        return NULL;
-    }
-
-    jbyteArray array = env->NewByteArray(mediaAlbumArt->size());
-    if (array != NULL) {
-        const jbyte* data =
-                reinterpret_cast<const jbyte*>(mediaAlbumArt->data());
-        env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data);
-    }
-
-    free(mediaAlbumArt);
-    // if NewByteArray() returned NULL, an out-of-memory
-    // exception will have been raised. I just want to
-    // return null in that case.
-    env->ExceptionClear();
-    return array;
-}
-
-// This function gets a field ID, which in turn causes class initialization.
-// It is called from a static block in MediaScanner, which won't run until the
-// first time an instance of this class is used.
-static void
-android_media_MediaScanner_native_init(JNIEnv *env)
-{
-    ALOGV("native_init");
-    jclass clazz = env->FindClass(kClassMediaScanner);
-    if (clazz == NULL) {
-        return;
-    }
-
-    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
-    if (fields.context == NULL) {
-        return;
-    }
-}
-
-static void
-android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz)
-{
-    ALOGV("native_setup");
-    MediaScanner *mp = new StagefrightMediaScanner;
-
-    if (mp == NULL) {
-        jniThrowException(env, kRunTimeException, "Out of memory");
-        return;
-    }
-
-    env->SetLongField(thiz, fields.context, (jlong)mp);
-}
-
-static void
-android_media_MediaScanner_native_finalize(JNIEnv *env, jobject thiz)
-{
-    ALOGV("native_finalize");
-    MediaScanner *mp = getNativeScanner_l(env, thiz);
-    if (mp == 0) {
-        return;
-    }
-    delete mp;
-    setNativeScanner_l(env, thiz, 0);
-}
-
-static const JNINativeMethod gMethods[] = {
-    {
-        "processDirectory",
-        "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
-        (void *)android_media_MediaScanner_processDirectory
-    },
-
-    {
-        "processFile",
-        "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)Z",
-        (void *)android_media_MediaScanner_processFile
-    },
-
-    {
-        "setLocale",
-        "(Ljava/lang/String;)V",
-        (void *)android_media_MediaScanner_setLocale
-    },
-
-    {
-        "extractAlbumArt",
-        "(Ljava/io/FileDescriptor;)[B",
-        (void *)android_media_MediaScanner_extractAlbumArt
-    },
-
-    {
-        "native_init",
-        "()V",
-        (void *)android_media_MediaScanner_native_init
-    },
-
-    {
-        "native_setup",
-        "()V",
-        (void *)android_media_MediaScanner_native_setup
-    },
-
-    {
-        "native_finalize",
-        "()V",
-        (void *)android_media_MediaScanner_native_finalize
-    },
-};
-
-// This function only registers the native methods, and is called from
-// JNI_OnLoad in android_media_MediaPlayer.cpp
-int register_android_media_MediaScanner(JNIEnv *env)
-{
-    return AndroidRuntime::registerNativeMethods(env,
-                kClassMediaScanner, gMethods, NELEM(gMethods));
-}
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 8d39a93..680c879 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -130,6 +130,33 @@
         }
     }
 
+    @Override
+    public void onSetVolume(String routeId, int volume) {
+        MediaRoute2Info route = mRoutes.get(routeId);
+        if (route == null) {
+            return;
+        }
+        volume = Math.min(volume, Math.max(0, route.getVolumeMax()));
+        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+                .setVolume(volume)
+                .build());
+        publishRoutes();
+    }
+
+    @Override
+    public void onUpdateVolume(String routeId, int delta) {
+        MediaRoute2Info route = mRoutes.get(routeId);
+        if (route == null) {
+            return;
+        }
+        int volume = route.getVolume() + delta;
+        volume = Math.min(volume, Math.max(0, route.getVolumeMax()));
+        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+                .setVolume(volume)
+                .build());
+        publishRoutes();
+    }
+
     void publishRoutes() {
         MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder()
                 .addRoutes(mRoutes.values())
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index da832ac..ca43d04 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -47,8 +47,10 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -190,17 +192,8 @@
         MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
         mManager.registerCallback(mExecutor, mockCallback);
 
-        MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class);
-
-        mRouter2.setControlCategories(CONTROL_CATEGORIES_SPECIAL);
-        mRouter2.registerCallback(mExecutor, mockRouterCallback);
-        mRouter2.unregisterCallback(mockRouterCallback);
-
-        verify(mockCallback, timeout(TIMEOUT_MS))
-                .onRoutesChanged(argThat(routes -> routes.size() > 0));
-
         Map<String, MediaRoute2Info> routes =
-                createRouteMap(mManager.getAvailableRoutes(mPackageName));
+                waitAndGetRoutesWithManager(CONTROL_CATEGORIES_SPECIAL);
 
         Assert.assertEquals(1, routes.size());
         Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
@@ -214,12 +207,10 @@
     @Test
     public void testGetRoutes() throws Exception {
         MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
-
-        mRouter2.setControlCategories(CONTROL_CATEGORIES_SPECIAL);
         mRouter2.registerCallback(mExecutor, mockCallback);
-        verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
-                .onRoutesChanged(argThat(routes -> routes.size() > 0));
-        Map<String, MediaRoute2Info> routes = createRouteMap(mRouter2.getRoutes());
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CONTROL_CATEGORIES_SPECIAL);
+
         Assert.assertEquals(1, routes.size());
         Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
 
@@ -228,51 +219,40 @@
 
     @Test
     public void testOnRouteSelected() throws Exception {
-        MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class);
+        MediaRouter2.Callback routerCallback = new MediaRouter2.Callback();
         MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
 
         mManager.registerCallback(mExecutor, managerCallback);
-        mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL);
-        mRouter2.registerCallback(mExecutor, mockRouterCallback);
+        mRouter2.registerCallback(mExecutor, routerCallback);
 
-        verify(managerCallback, timeout(TIMEOUT_MS))
-                .onRoutesChanged(argThat(routes -> routes.size() > 0));
-
-        Map<String, MediaRoute2Info> routes =
-                createRouteMap(mManager.getAvailableRoutes(mPackageName));
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CONTROL_CATEGORIES_ALL);
 
         MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
-        mManager.selectRoute(mPackageName, routeToSelect);
-
         assertNotNull(routeToSelect);
+
+        mManager.selectRoute(mPackageName, routeToSelect);
         verify(managerCallback, timeout(TIMEOUT_MS))
                 .onRouteAdded(argThat(route -> route.equals(routeToSelect)));
 
+        mRouter2.unregisterCallback(routerCallback);
         mManager.unregisterCallback(managerCallback);
-        mRouter2.unregisterCallback(mockRouterCallback);
     }
 
     /**
      * Tests selecting and unselecting routes of a single provider.
      */
     @Test
-    public void testSingleProviderSelect() {
+    public void testSingleProviderSelect() throws Exception {
         MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
         MediaRouter2.Callback routerCallback = mock(MediaRouter2.Callback.class);
 
         mManager.registerCallback(mExecutor, managerCallback);
-        mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL);
         mRouter2.registerCallback(mExecutor, routerCallback);
 
-        verify(managerCallback, timeout(TIMEOUT_MS))
-                .onRoutesChanged(argThat(routes -> routes.size() > 0));
-
-        Map<String, MediaRoute2Info> routes =
-                createRouteMap(mManager.getAvailableRoutes(mPackageName));
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CONTROL_CATEGORIES_ALL);
 
         mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1));
-        verify(managerCallback, timeout(TIMEOUT_MS)
-        )
+        verify(managerCallback, timeout(TIMEOUT_MS))
                 .onRouteChanged(argThat(routeInfo -> TextUtils.equals(ROUTE_ID1, routeInfo.getId())
                         && TextUtils.equals(routeInfo.getClientPackageName(), mPackageName)));
 
@@ -291,14 +271,67 @@
     }
 
     @Test
-    public void testVolumeHandling() {
+    public void testControlVolumeWithRouter() throws Exception {
         MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
 
-        mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CONTROL_CATEGORIES_ALL);
         mRouter2.registerCallback(mExecutor, mockCallback);
+
+        MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+        int originalVolume = volRoute.getVolume();
+        int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
+        int targetVolume = originalVolume + deltaVolume;
+
+        mRouter2.requestSetVolume(volRoute, targetVolume);
         verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
-                .onRoutesChanged(argThat(routes -> routes.size() > 0));
-        Map<String, MediaRoute2Info> routes = createRouteMap(mRouter2.getRoutes());
+                .onRouteChanged(argThat(route ->
+                        route.getId().equals(volRoute.getId())
+                                && route.getVolume() == targetVolume));
+
+        mRouter2.requestUpdateVolume(volRoute, -deltaVolume);
+        verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
+                .onRouteChanged(argThat(route ->
+                        route.getId().equals(volRoute.getId())
+                                && route.getVolume() == originalVolume));
+
+        mRouter2.unregisterCallback(mockCallback);
+    }
+
+    @Test
+    public void testControlVolumeWithManager() throws Exception {
+        MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
+        MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
+
+        mManager.registerCallback(mExecutor, managerCallback);
+        mRouter2.registerCallback(mExecutor, mockCallback);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CONTROL_CATEGORIES_ALL);
+
+        MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+        int originalVolume = volRoute.getVolume();
+        int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
+        int targetVolume = originalVolume + deltaVolume;
+
+        mManager.requestSetVolume(volRoute, targetVolume);
+        verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
+                .onRouteChanged(argThat(route ->
+                        route.getId().equals(volRoute.getId())
+                                && route.getVolume() == targetVolume));
+
+        mManager.requestUpdateVolume(volRoute, -deltaVolume);
+        verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
+                .onRouteChanged(argThat(route ->
+                        route.getId().equals(volRoute.getId())
+                                && route.getVolume() == originalVolume));
+
+        mRouter2.unregisterCallback(mockCallback);
+        mManager.unregisterCallback(managerCallback);
+    }
+
+    @Test
+    public void testVolumeHandling() throws Exception {
+        MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
+        mRouter2.registerCallback(mExecutor, mockCallback);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CONTROL_CATEGORIES_ALL);
 
         MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
         MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
@@ -310,6 +343,48 @@
         mRouter2.unregisterCallback(mockCallback);
     }
 
+    Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories) throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        MediaRouter2.Callback callback = new MediaRouter2.Callback() {
+            @Override
+            public void onRoutesChanged(List<MediaRoute2Info> routes) {
+                if (routes.size() > 0) latch.countDown();
+            }
+        };
+        mRouter2.setControlCategories(controlCategories);
+        mRouter2.registerCallback(mExecutor, callback);
+        try {
+            latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            return createRouteMap(mRouter2.getRoutes());
+        } finally {
+            mRouter2.unregisterCallback(callback);
+        }
+    }
+
+    Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> controlCategories)
+            throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        // Dummy callback is required to send control category info.
+        MediaRouter2.Callback routerCallback = new MediaRouter2.Callback();
+        MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
+            @Override
+            public void onRoutesChanged(List<MediaRoute2Info> routes) {
+                if (routes.size() > 0) latch.countDown();
+            }
+        };
+        mManager.registerCallback(mExecutor, managerCallback);
+        mRouter2.setControlCategories(controlCategories);
+        mRouter2.registerCallback(mExecutor, routerCallback);
+        try {
+            latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            return createRouteMap(mManager.getAvailableRoutes(mPackageName));
+        } finally {
+            mRouter2.unregisterCallback(routerCallback);
+            mManager.unregisterCallback(managerCallback);
+        }
+    }
+
     // Helper for getting routes easily
     static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
         Map<String, MediaRoute2Info> routeMap = new HashMap<>();
diff --git a/packages/BackupEncryption/Android.bp b/packages/BackupEncryption/Android.bp
index 342d796..68e937c 100644
--- a/packages/BackupEncryption/Android.bp
+++ b/packages/BackupEncryption/Android.bp
@@ -17,8 +17,7 @@
 android_app {
     name: "BackupEncryption",
     srcs: ["src/**/*.java"],
-    libs: ["backup-encryption-protos"],
-    static_libs: ["backuplib"],
+    static_libs: ["backup-encryption-protos", "backuplib"],
     optimize: { enabled: false },
     platform_apis: true,
     certificate: "platform",
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/EncryptionKeyHelper.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/EncryptionKeyHelper.java
new file mode 100644
index 0000000..2035b66
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/EncryptionKeyHelper.java
@@ -0,0 +1,81 @@
+/*
+ * 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.backup.encryption;
+
+import android.content.Context;
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+import android.security.keystore.recovery.RecoveryController;
+
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
+import com.android.server.backup.encryption.keys.TertiaryKeyManager;
+import com.android.server.backup.encryption.keys.TertiaryKeyRotationScheduler;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+class EncryptionKeyHelper {
+    private static SecureRandom sSecureRandom = new  SecureRandom();
+
+    private final Context mContext;
+    private final RecoverableKeyStoreSecondaryKeyManager
+            .RecoverableKeyStoreSecondaryKeyManagerProvider
+            mSecondaryKeyManagerProvider;
+
+    EncryptionKeyHelper(Context context) {
+        mContext = context;
+        mSecondaryKeyManagerProvider =
+                () ->
+                        new RecoverableKeyStoreSecondaryKeyManager(
+                                RecoveryController.getInstance(mContext), sSecureRandom);
+    }
+
+    RecoverableKeyStoreSecondaryKeyManager
+            .RecoverableKeyStoreSecondaryKeyManagerProvider getKeyManagerProvider() {
+        return mSecondaryKeyManagerProvider;
+    }
+
+    RecoverableKeyStoreSecondaryKey getActiveSecondaryKey()
+            throws UnrecoverableKeyException, InternalRecoveryServiceException {
+        String keyAlias = CryptoSettings.getInstance(mContext).getActiveSecondaryKeyAlias().get();
+        return mSecondaryKeyManagerProvider.get().get(keyAlias).get();
+    }
+
+    SecretKey getTertiaryKey(
+            String packageName,
+            RecoverableKeyStoreSecondaryKey secondaryKey)
+            throws IllegalBlockSizeException, InvalidAlgorithmParameterException,
+            NoSuchAlgorithmException, IOException, NoSuchPaddingException,
+            InvalidKeyException {
+        TertiaryKeyManager tertiaryKeyManager =
+                new TertiaryKeyManager(
+                        mContext,
+                        sSecureRandom,
+                        TertiaryKeyRotationScheduler.getInstance(mContext),
+                        secondaryKey,
+                        packageName);
+        return tertiaryKeyManager.getKey();
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java
new file mode 100644
index 0000000..1d841b4
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java
@@ -0,0 +1,143 @@
+/*
+ * 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.backup.encryption;
+
+import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import com.android.server.backup.encryption.client.CryptoBackupServer;
+import com.android.server.backup.encryption.keys.KeyWrapUtils;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+import com.android.server.backup.encryption.tasks.EncryptedKvBackupTask;
+import com.android.server.backup.encryption.tasks.EncryptedKvRestoreTask;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Map;
+
+public class KeyValueEncrypter {
+    private final Context mContext;
+    private final EncryptionKeyHelper mKeyHelper;
+
+    public KeyValueEncrypter(Context context) {
+        mContext = context;
+        mKeyHelper = new EncryptionKeyHelper(mContext);
+    }
+
+    public void encryptKeyValueData(
+            String packageName, ParcelFileDescriptor inputFd, OutputStream outputStream)
+            throws Exception {
+        EncryptedKvBackupTask.EncryptedKvBackupTaskFactory backupTaskFactory =
+                new EncryptedKvBackupTask.EncryptedKvBackupTaskFactory();
+        EncryptedKvBackupTask backupTask =
+                backupTaskFactory.newInstance(
+                        mContext,
+                        new SecureRandom(),
+                        new FileBackupServer(outputStream),
+                        CryptoSettings.getInstance(mContext),
+                        mKeyHelper.getKeyManagerProvider(),
+                        inputFd,
+                        packageName);
+        backupTask.performBackup(/* incremental */ false);
+    }
+
+    public void decryptKeyValueData(String packageName,
+            InputStream encryptedInputStream, ParcelFileDescriptor outputFd) throws Exception {
+        RecoverableKeyStoreSecondaryKey secondaryKey = mKeyHelper.getActiveSecondaryKey();
+
+        EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory restoreTaskFactory =
+                new EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory();
+        EncryptedKvRestoreTask restoreTask =
+                restoreTaskFactory.newInstance(
+                        mContext,
+                        mKeyHelper.getKeyManagerProvider(),
+                        new InputStreamFullRestoreDownloader(encryptedInputStream),
+                        secondaryKey.getAlias(),
+                        KeyWrapUtils.wrap(
+                                secondaryKey.getSecretKey(),
+                                mKeyHelper.getTertiaryKey(packageName, secondaryKey)));
+
+        restoreTask.getRestoreData(outputFd);
+    }
+
+    // TODO(b/142455725): Extract into a commong class.
+    private static class FileBackupServer implements CryptoBackupServer {
+        private static final String EMPTY_DOC_ID = "";
+
+        private final OutputStream mOutputStream;
+
+        FileBackupServer(OutputStream outputStream) {
+            mOutputStream = outputStream;
+        }
+
+        @Override
+        public String uploadIncrementalBackup(
+                String packageName,
+                String oldDocId,
+                byte[] diffScript,
+                WrappedKeyProto.WrappedKey tertiaryKey) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String uploadNonIncrementalBackup(
+                String packageName, byte[] data, WrappedKeyProto.WrappedKey tertiaryKey) {
+            try {
+                mOutputStream.write(data);
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to write encrypted data to file: ", e);
+            }
+
+            return EMPTY_DOC_ID;
+        }
+
+        @Override
+        public void setActiveSecondaryKeyAlias(
+                String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys) {
+            // Do nothing.
+        }
+    }
+
+    // TODO(b/142455725): Extract into a commong class.
+    private static class InputStreamFullRestoreDownloader extends FullRestoreDownloader {
+        private final InputStream mInputStream;
+
+        InputStreamFullRestoreDownloader(InputStream inputStream) {
+            mInputStream = inputStream;
+        }
+
+        @Override
+        public int readNextChunk(byte[] buffer) throws IOException {
+            return mInputStream.read(buffer);
+        }
+
+        @Override
+        public void finish(FinishType finishType) {
+            try {
+                mInputStream.close();
+            } catch (IOException e) {
+                Log.w(TAG, "Error while reading restore data");
+            }
+        }
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java
index 1d0224d..c3cb335 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java
@@ -18,27 +18,58 @@
 
 import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
 
+import android.app.backup.BackupTransport;
+import android.app.backup.RestoreDescription;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.encryption.KeyValueEncrypter;
 import com.android.server.backup.transport.DelegatingTransport;
 import com.android.server.backup.transport.TransportClient;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicReference;
+
 /**
  * This is an implementation of {@link IBackupTransport} that encrypts (or decrypts) the data when
  * sending it (or receiving it) from the {@link IBackupTransport} returned by {@link
  * TransportClient.connect(String)}.
  */
 public class IntermediateEncryptingTransport extends DelegatingTransport {
+    private static final String BACKUP_TEMP_DIR = "backup";
+    private static final String RESTORE_TEMP_DIR = "restore";
+
     private final TransportClient mTransportClient;
     private final Object mConnectLock = new Object();
+    private final Context mContext;
     private volatile IBackupTransport mRealTransport;
+    private AtomicReference<String> mNextRestorePackage = new AtomicReference<>();
+    private final KeyValueEncrypter mKeyValueEncrypter;
+    private final boolean mShouldEncrypt;
+
+    IntermediateEncryptingTransport(
+            TransportClient transportClient, Context context, boolean shouldEncrypt) {
+        this(transportClient, context, new KeyValueEncrypter(context), shouldEncrypt);
+    }
 
     @VisibleForTesting
-    IntermediateEncryptingTransport(TransportClient transportClient) {
+    IntermediateEncryptingTransport(
+            TransportClient transportClient, Context context, KeyValueEncrypter keyValueEncrypter,
+            boolean shouldEncrypt) {
         mTransportClient = transportClient;
+        mContext = context;
+        mKeyValueEncrypter = keyValueEncrypter;
+        mShouldEncrypt = shouldEncrypt;
     }
 
     @Override
@@ -46,9 +77,116 @@
         if (mRealTransport == null) {
             connect();
         }
+        Log.d(TAG, "real transport = " + mRealTransport.name());
         return mRealTransport;
     }
 
+    @Override
+    public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags)
+            throws RemoteException {
+        if (!mShouldEncrypt) {
+            return super.performBackup(packageInfo, inFd, flags);
+        }
+
+        File encryptedStorageFile = getBackupTempStorage(packageInfo.packageName);
+        if (encryptedStorageFile == null) {
+            return BackupTransport.TRANSPORT_ERROR;
+        }
+
+        // Encrypt the backup data and write it into a temp file.
+        try (OutputStream encryptedOutput = new FileOutputStream(encryptedStorageFile)) {
+            mKeyValueEncrypter.encryptKeyValueData(packageInfo.packageName, inFd,
+                    encryptedOutput);
+        } catch (Throwable e) {
+            Log.e(TAG, "Failed to encrypt backup data: ", e);
+            return BackupTransport.TRANSPORT_ERROR;
+        }
+
+        // Pass the temp file to the real transport for backup.
+        try (FileInputStream encryptedInput = new FileInputStream(encryptedStorageFile)) {
+            return super.performBackup(
+                    packageInfo, ParcelFileDescriptor.dup(encryptedInput.getFD()), flags);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to read encrypted data from temp storage: ", e);
+            return BackupTransport.TRANSPORT_ERROR;
+        }
+    }
+
+    @Override
+    public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
+        if (!mShouldEncrypt) {
+            return super.getRestoreData(outFd);
+        }
+
+        String nextRestorePackage = mNextRestorePackage.get();
+        if (nextRestorePackage == null) {
+            Log.e(TAG, "No next restore package set");
+            return BackupTransport.TRANSPORT_ERROR;
+        }
+
+        File encryptedStorageFile = getRestoreTempStorage(nextRestorePackage);
+        if (encryptedStorageFile == null) {
+            return BackupTransport.TRANSPORT_ERROR;
+        }
+
+        // Get encrypted restore data from the real transport and write it into a temp file.
+        try (FileOutputStream outputStream = new FileOutputStream(encryptedStorageFile)) {
+            int status = super.getRestoreData(ParcelFileDescriptor.dup(outputStream.getFD()));
+            if (status != BackupTransport.TRANSPORT_OK) {
+                Log.e(TAG, "Failed to read restore data from transport, status = " + status);
+                return status;
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to write encrypted data to temp storage: ", e);
+            return BackupTransport.TRANSPORT_ERROR;
+        }
+
+        // Decrypt the data and write it into the fd given by the real transport.
+        try (InputStream inputStream = new FileInputStream(encryptedStorageFile)) {
+            mKeyValueEncrypter.decryptKeyValueData(nextRestorePackage, inputStream, outFd);
+            encryptedStorageFile.delete();
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to decrypt restored data: ", e);
+            return BackupTransport.TRANSPORT_ERROR;
+        }
+
+        return BackupTransport.TRANSPORT_OK;
+    }
+
+    @Override
+    public RestoreDescription nextRestorePackage() throws RemoteException {
+        if (!mShouldEncrypt) {
+            return super.nextRestorePackage();
+        }
+
+        RestoreDescription restoreDescription = super.nextRestorePackage();
+        mNextRestorePackage.set(restoreDescription.getPackageName());
+
+        return restoreDescription;
+    }
+
+    @VisibleForTesting
+    protected File getBackupTempStorage(String packageName) {
+        return getTempStorage(packageName, BACKUP_TEMP_DIR);
+    }
+
+    @VisibleForTesting
+    protected File getRestoreTempStorage(String packageName) {
+        return getTempStorage(packageName, RESTORE_TEMP_DIR);
+    }
+
+    private File getTempStorage(String packageName, String operationType) {
+        File encryptedDir = new File(mContext.getFilesDir(), operationType);
+        encryptedDir.mkdir();
+        File encryptedFile = new File(encryptedDir, packageName);
+        try {
+            encryptedFile.createNewFile();
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to create temp file for encrypted data: ", e);
+        }
+        return encryptedFile;
+    }
+
     private void connect() throws RemoteException {
         Log.i(TAG, "connecting " + mTransportClient);
         synchronized (mConnectLock) {
@@ -65,4 +203,9 @@
     TransportClient getClient() {
         return mTransportClient;
     }
+
+    @VisibleForTesting
+    void setNextRestorePackage(String nextRestorePackage) {
+        mNextRestorePackage.set(nextRestorePackage);
+    }
 }
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java
index 6e6d571..7c4082c 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java
@@ -26,20 +26,20 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.backup.IBackupTransport;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.server.backup.transport.TransportClientManager;
 import com.android.server.backup.transport.TransportStats;
 
 import java.util.HashMap;
 import java.util.Map;
 
-/**
- * Handles creation and cleanup of {@link IntermediateEncryptingTransport} instances.
- */
+/** Handles creation and cleanup of {@link IntermediateEncryptingTransport} instances. */
 public class IntermediateEncryptingTransportManager {
     private static final String CALLER = "IntermediateEncryptingTransportManager";
     private final TransportClientManager mTransportClientManager;
     private final Object mTransportsLock = new Object();
     private final Map<ComponentName, IntermediateEncryptingTransport> mTransports = new HashMap<>();
+    private Context mContext;
 
     @VisibleForTesting
     IntermediateEncryptingTransportManager(TransportClientManager transportClientManager) {
@@ -48,6 +48,7 @@
 
     public IntermediateEncryptingTransportManager(Context context) {
         this(new TransportClientManager(UserHandle.myUserId(), context, new TransportStats()));
+        mContext = context;
     }
 
     /**
@@ -55,31 +56,42 @@
      * provide a {@link IntermediateEncryptingTransport} which is an implementation of {@link
      * IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from
      * the real {@link IBackupTransport}.
+     *
      * @param intent {@link Intent} created with a call to {@link
-     * TransportClientManager.getEncryptingTransportIntent(ComponentName)}.
+     *     TransportClientManager.getEncryptingTransportIntent(ComponentName)}.
      * @return
      */
     public IntermediateEncryptingTransport get(Intent intent) {
         Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
         Log.i(TAG, "get: intent:" + intent + " transportIntent:" + transportIntent);
         synchronized (mTransportsLock) {
-            return mTransports.computeIfAbsent(transportIntent.getComponent(),
-                    c -> create(transportIntent));
+            return mTransports.computeIfAbsent(
+                    transportIntent.getComponent(), c -> create(transportIntent));
         }
     }
 
-    /**
-     * Create an instance of {@link IntermediateEncryptingTransport}.
-     */
+    /** Create an instance of {@link IntermediateEncryptingTransport}. */
     private IntermediateEncryptingTransport create(Intent realTransportIntent) {
         Log.d(TAG, "create: intent:" + realTransportIntent);
-        return new IntermediateEncryptingTransport(mTransportClientManager.getTransportClient(
-                realTransportIntent.getComponent(), realTransportIntent.getExtras(), CALLER));
+
+        LockPatternUtils patternUtils = new LockPatternUtils(mContext);
+        boolean shouldEncrypt =
+                realTransportIntent.getComponent().getClassName().contains("EncryptedLocalTransport")
+                        && (patternUtils.isLockPatternEnabled(UserHandle.myUserId())
+                                || patternUtils.isLockPasswordEnabled(UserHandle.myUserId()));
+
+        return new IntermediateEncryptingTransport(
+                mTransportClientManager.getTransportClient(
+                        realTransportIntent.getComponent(),
+                        realTransportIntent.getExtras(),
+                        CALLER),
+                mContext,
+                shouldEncrypt);
     }
 
     /**
-     * Cleanup the {@link IntermediateEncryptingTransport} which was created by a call to
-     * {@link #get(Intent)} with this {@link Intent}.
+     * Cleanup the {@link IntermediateEncryptingTransport} which was created by a call to {@link
+     * #get(Intent)} with this {@link Intent}.
      */
     public void cleanup(Intent intent) {
         Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
diff --git a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java
index cc4b0ab..a85b2e4 100644
--- a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java
+++ b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java
@@ -18,43 +18,71 @@
 
 import static junit.framework.Assert.assertEquals;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.backup.BackupTransport;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.encryption.KeyValueEncrypter;
 import com.android.server.backup.transport.TransportClient;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.File;
+
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class IntermediateEncryptingTransportTest {
-    @Mock private IBackupTransport mRealTransport;
-    @Mock private TransportClient mTransportClient;
+    private static final String TEST_PACKAGE_NAME = "test_package";
 
     private IntermediateEncryptingTransport mIntermediateEncryptingTransport;
+    private final PackageInfo mTestPackage = new PackageInfo();
+
+    @Mock private IBackupTransport mRealTransport;
+    @Mock private TransportClient mTransportClient;
+    @Mock private ParcelFileDescriptor mParcelFileDescriptor;
+    @Mock private KeyValueEncrypter mKeyValueEncrypter;
+    @Mock private Context mContext;
+
+    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    private File mTempFile;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mIntermediateEncryptingTransport = new IntermediateEncryptingTransport(mTransportClient);
+
+        mIntermediateEncryptingTransport =
+                new IntermediateEncryptingTransport(
+                        mTransportClient, mContext, mKeyValueEncrypter, true);
+        mTestPackage.packageName = TEST_PACKAGE_NAME;
+        mTempFile = mTemporaryFolder.newFile();
+
+        when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
+        when(mRealTransport.getRestoreData(any())).thenReturn(BackupTransport.TRANSPORT_OK);
     }
 
     @Test
     public void testGetDelegate_callsConnect() throws Exception {
-        when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
-
         IBackupTransport ret = mIntermediateEncryptingTransport.getDelegate();
 
         assertEquals(mRealTransport, ret);
@@ -74,4 +102,79 @@
         verify(mTransportClient, times(1)).connect(anyString());
         verifyNoMoreInteractions(mTransportClient);
     }
+
+    @Test
+    public void testPerformBackup_shouldEncryptTrue_encryptsDataAndPassesToDelegate()
+            throws Exception {
+        mIntermediateEncryptingTransport =
+                new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true);
+
+        mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0);
+
+        verify(mKeyValueEncrypter, times(1))
+                .encryptKeyValueData(eq(TEST_PACKAGE_NAME), eq(mParcelFileDescriptor), any());
+        verify(mRealTransport, times(1)).performBackup(eq(mTestPackage), any(), eq(0));
+    }
+
+    @Test
+    public void testPerformBackup_shouldEncryptFalse_doesntEncryptDataAndPassedToDelegate()
+            throws Exception {
+        mIntermediateEncryptingTransport =
+                new TestIntermediateTransport(
+                        mTransportClient, mContext, mKeyValueEncrypter, false);
+
+        mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0);
+
+        verifyZeroInteractions(mKeyValueEncrypter);
+        verify(mRealTransport, times(1))
+                .performBackup(eq(mTestPackage), eq(mParcelFileDescriptor), eq(0));
+    }
+
+    @Test
+    public void testGetRestoreData_shouldEncryptTrue_decryptsDataAndPassesToDelegate()
+            throws Exception {
+        mIntermediateEncryptingTransport =
+                new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true);
+        mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME);
+
+        mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor);
+
+        verify(mKeyValueEncrypter, times(1))
+                .decryptKeyValueData(eq(TEST_PACKAGE_NAME), any(), eq(mParcelFileDescriptor));
+        verify(mRealTransport, times(1)).getRestoreData(any());
+    }
+
+    @Test
+    public void testGetRestoreData_shouldEncryptFalse_doesntDecryptDataAndPassesToDelegate()
+            throws Exception {
+        mIntermediateEncryptingTransport =
+                new TestIntermediateTransport(
+                        mTransportClient, mContext, mKeyValueEncrypter, false);
+        mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME);
+
+        mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor);
+
+        verifyZeroInteractions(mKeyValueEncrypter);
+        verify(mRealTransport, times(1)).getRestoreData(eq(mParcelFileDescriptor));
+    }
+
+    private final class TestIntermediateTransport extends IntermediateEncryptingTransport {
+        TestIntermediateTransport(
+                TransportClient transportClient,
+                Context context,
+                KeyValueEncrypter keyValueEncrypter,
+                boolean shouldEncrypt) {
+            super(transportClient, context, keyValueEncrypter, shouldEncrypt);
+        }
+
+        @Override
+        protected File getBackupTempStorage(String packageName) {
+            return mTempFile;
+        }
+
+        @Override
+        protected File getRestoreTempStorage(String packageName) {
+            return mTempFile;
+        }
+    }
 }
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/res/layout/car_top_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
new file mode 100644
index 0000000..8247211
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/car_top_bar"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/system_bar_background"
+    android:orientation="vertical">
+
+  <RelativeLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_weight="1">
+
+    <FrameLayout
+        android:id="@+id/left_hvac_container"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_alignParentStart="true"
+        >
+
+      <com.android.systemui.statusbar.car.CarNavigationButton
+          android:id="@+id/hvacleft"
+          android:layout_width="match_parent"
+          android:layout_height="match_parent"
+          android:background="@null"
+          systemui:broadcast="true"
+          systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+          />
+
+      <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+          android:id="@+id/lefttext"
+          android:layout_width="wrap_content"
+          android:layout_height="match_parent"
+          android:paddingStart="@*android:dimen/car_padding_4"
+          android:paddingEnd="16dp"
+          android:gravity="center_vertical|start"
+          android:minEms="4"
+          android:textAppearance="@style/TextAppearance.CarStatus"
+          systemui:hvacAreaId="49"
+          systemui:hvacMaxText="@string/hvac_max_text"
+          systemui:hvacMaxValue="@dimen/hvac_max_value"
+          systemui:hvacMinText="@string/hvac_min_text"
+          systemui:hvacMinValue="@dimen/hvac_min_value"
+          systemui:hvacPivotOffset="60dp"
+          systemui:hvacPropertyId="358614275"
+          systemui:hvacTempFormat="%.0f\u00B0"
+          />
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/clock_container"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_centerInParent="true">
+      <com.android.systemui.statusbar.car.CarNavigationButton
+          android:id="@+id/qs"
+          android:layout_width="match_parent"
+          android:layout_height="match_parent"
+          android:background="@null"/>
+      <com.android.systemui.statusbar.policy.Clock
+          android:id="@+id/clock"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:layout_gravity="center"
+          android:elevation="5dp"
+          android:singleLine="true"
+          android:textAppearance="@style/TextAppearance.StatusBar.Clock"/>
+    </FrameLayout>
+
+    <LinearLayout
+        android:id="@+id/system_icon_area"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_centerHorizontal="true"
+        android:layout_centerVertical="true"
+        android:layout_toEndOf="@+id/clock_container"
+        android:paddingStart="@*android:dimen/car_padding_1"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+        >
+
+      <include
+          layout="@layout/system_icons"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:layout_weight="1"
+          android:paddingStart="4dp"
+          android:gravity="center_vertical"
+          />
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/right_hvac_container"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_alignParentEnd="true"
+        >
+
+      <com.android.systemui.statusbar.car.CarNavigationButton
+          android:id="@+id/hvacright"
+          android:layout_width="match_parent"
+          android:layout_height="match_parent"
+          android:background="@null"
+          systemui:broadcast="true"
+          systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+          />
+
+      <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+          android:id="@+id/righttext"
+          android:layout_width="wrap_content"
+          android:layout_height="match_parent"
+          android:paddingStart="16dp"
+          android:paddingEnd="@*android:dimen/car_padding_4"
+          android:gravity="center_vertical|end"
+          android:minEms="4"
+          android:textAppearance="@style/TextAppearance.CarStatus"
+          systemui:hvacAreaId="68"
+          systemui:hvacMaxText="@string/hvac_max_text"
+          systemui:hvacMaxValue="@dimen/hvac_max_value"
+          systemui:hvacMinText="@string/hvac_min_text"
+          systemui:hvacMinValue="@dimen/hvac_min_value"
+          systemui:hvacPivotOffset="60dp"
+          systemui:hvacPropertyId="358614275"
+          systemui:hvacTempFormat="%.0f\u00B0"
+          />
+    </FrameLayout>
+  </RelativeLayout>
+
+</com.android.systemui.statusbar.car.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml
index 08d48bf..37cd1d4 100644
--- a/packages/CarSystemUI/res/layout/super_status_bar.xml
+++ b/packages/CarSystemUI/res/layout/super_status_bar.xml
@@ -69,10 +69,10 @@
             android:visibility="gone"
         />
 
-        <include layout="@layout/car_top_navigation_bar"
+        <FrameLayout
+            android:id="@+id/car_top_navigation_bar_container"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-        />
+            android:layout_height="wrap_content"/>
     </LinearLayout>
 
     <include layout="@layout/brightness_mirror"/>
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
index fb422af..e2da91b 100644
--- a/packages/CarSystemUI/res/values/dimens.xml
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -120,4 +120,73 @@
     <dimen name="notification_shade_handle_bar_margin_bottom">10dp</dimen>
     <dimen name="notification_shade_list_padding_bottom">50dp</dimen>
 
+    <!-- The alpha for the scrim behind the notification shade. This value is 1 so that the
+     scrim has no transparency. -->
+    <item name="scrim_behind_alpha" format="float" type="dimen">1.0</item>
+
+    <!-- The width of panel holding the notification card. -->
+    <dimen name="notification_panel_width">522dp</dimen>
+
+    <!-- The width of the quick settings panel. -1 for match_parent. -->
+    <dimen name="qs_panel_width">-1px</dimen>
+
+    <!-- Height of a small notification in the status bar-->
+    <dimen name="notification_min_height">192dp</dimen>
+
+    <!-- Height of a small notification in the status bar which was used before android N -->
+    <dimen name="notification_min_height_legacy">192dp</dimen>
+
+    <!-- Height of a large notification in the status bar -->
+    <dimen name="notification_max_height">400dp</dimen>
+
+    <!-- Height of a heads up notification in the status bar for legacy custom views -->
+    <dimen name="notification_max_heads_up_height_legacy">400dp</dimen>
+
+    <!-- Height of a heads up notification in the status bar -->
+    <dimen name="notification_max_heads_up_height">400dp</dimen>
+
+    <!-- Height of the status bar header bar -->
+    <dimen name="status_bar_header_height">54dp</dimen>
+
+    <!-- The height of the divider between the individual notifications. -->
+    <dimen name="notification_divider_height">16dp</dimen>
+
+    <!-- The height of the divider between the individual notifications when the notification
+         wants it to be increased. This value is the same as notification_divider_height so that
+         the spacing between all notifications will always be the same. -->
+    <dimen name="notification_divider_height_increased">@dimen/notification_divider_height</dimen>
+
+    <!-- The alpha of the dividing line between child notifications of a notification group. -->
+    <item name="notification_divider_alpha" format="float" type="dimen">1.0</item>
+
+    <!-- The width of each individual notification card. -->
+    <dimen name="notification_child_width">522dp</dimen>
+
+    <!-- The top margin of the notification panel. -->
+    <dimen name="notification_panel_margin_top">32dp</dimen>
+
+    <!-- The bottom margin of the panel that holds the list of notifications. -->
+    <dimen name="notification_panel_margin_bottom">@dimen/notification_divider_height</dimen>
+
+    <!-- The corner radius of the shadow behind the notification. -->
+    <dimen name="notification_shadow_radius">16dp</dimen>
+
+    <!-- The amount of space below the notification list. This value is 0 so the list scrolls
+         all the way to the bottom. -->
+    <dimen name="close_handle_underlap">0dp</dimen>
+
+    <!-- The height of the divider between the individual notifications in a notification group. -->
+    <dimen name="notification_children_container_divider_height">1dp</dimen>
+
+    <!-- The height of the header for a container containing child notifications. -->
+    <dimen name="notification_children_container_header_height">76dp</dimen>
+
+    <!-- The top margin for the notification children container in its non-expanded form. This
+         value is smaller than notification_children_container_header_height to bring the first
+         child closer so there is less wasted space. -->
+    <dimen name="notification_children_container_margin_top">68dp</dimen>
+
+    <!-- The height of the quick settings footer that holds the user switcher, settings icon,
+         etc. in the car setting.-->
+    <dimen name="qs_footer_height">74dp</dimen>
 </resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
index c7654e8..be2d542 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
@@ -23,8 +23,6 @@
 import com.android.systemui.statusbar.car.CarFacetButtonController;
 import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.volume.CarVolumeDialogComponent;
-import com.android.systemui.volume.VolumeDialogComponent;
 
 import javax.inject.Singleton;
 
@@ -57,10 +55,6 @@
         return new CarStatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils);
     }
 
-    public VolumeDialogComponent createVolumeDialogComponent(SystemUI systemUi, Context context) {
-        return new CarVolumeDialogComponent(systemUi, context);
-    }
-
     @Singleton
     @Component(modules = ContextHolder.class)
     public interface CarDependencyComponent {
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index 5ec1bae..b1067f8 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -36,6 +36,8 @@
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.volume.CarVolumeDialogComponent;
+import com.android.systemui.volume.VolumeDialogComponent;
 
 import javax.inject.Named;
 import javax.inject.Singleton;
@@ -102,4 +104,8 @@
     @IntoMap
     @ClassKey(StatusBar.class)
     public abstract SystemUI providesStatusBar(CarStatusBar statusBar);
+
+    @Binds
+    abstract VolumeDialogComponent bindVolumeDialogComponent(
+            CarVolumeDialogComponent carVolumeDialogComponent);
 }
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 681d8f5..90aba2f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -113,6 +113,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -162,9 +163,11 @@
     private BatteryMeterView mBatteryMeterView;
     private Drawable mNotificationPanelBackground;
 
+    private ViewGroup mTopNavigationBarContainer;
     private ViewGroup mNavigationBarWindow;
     private ViewGroup mLeftNavigationBarWindow;
     private ViewGroup mRightNavigationBarWindow;
+    private CarNavigationBarView mTopNavigationBarView;
     private CarNavigationBarView mNavigationBarView;
     private CarNavigationBarView mLeftNavigationBarView;
     private CarNavigationBarView mRightNavigationBarView;
@@ -176,7 +179,7 @@
     private CarFacetButtonController mCarFacetButtonController;
     private ActivityManagerWrapper mActivityManagerWrapper;
     private DeviceProvisionedController mDeviceProvisionedController;
-    private boolean mDeviceIsProvisioned = true;
+    private boolean mDeviceIsSetUpForUser = true;
     private HvacController mHvacController;
     private DrivingStateHelper mDrivingStateHelper;
     private PowerManagerHelper mPowerManagerHelper;
@@ -197,6 +200,9 @@
     private boolean mNotificationListAtBottom;
     // Was the notification list at the bottom when the user first touched the screen
     private boolean mNotificationListAtBottomAtTimeOfTouch;
+    // To be attached to the top navigation bar (i.e. status bar) to pull down the notification
+    // panel.
+    private View.OnTouchListener mTopNavBarNotificationTouchListener;
     // To be attached to the navigation bars such that they can close the notification panel if
     // it's open.
     private View.OnTouchListener mNavBarNotificationTouchListener;
@@ -245,6 +251,7 @@
 
     @Inject
     public CarStatusBar(
+            Context context,
             LightBarController lightBarController,
             AutoHideController autoHideController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -298,8 +305,10 @@
             ConfigurationController configurationController,
             StatusBarWindowController statusBarWindowController,
             StatusBarWindowViewController.Builder statusBarWindowViewControllerBuild,
-            NotifLog notifLog) {
+            NotifLog notifLog,
+            DozeParameters dozeParameters) {
         super(
+                context,
                 lightBarController,
                 autoHideController,
                 keyguardUpdateMonitor,
@@ -353,7 +362,8 @@
                 configurationController,
                 statusBarWindowController,
                 statusBarWindowViewControllerBuild,
-                notifLog);
+                notifLog,
+                dozeParameters);
         mNavigationBarController = navigationBarController;
     }
 
@@ -362,7 +372,7 @@
         // get the provisioned state before calling the parent class since it's that flow that
         // builds the nav bar
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
-        mDeviceIsProvisioned = mDeviceProvisionedController.isDeviceProvisioned();
+        mDeviceIsSetUpForUser = mDeviceProvisionedController.isCurrentUserSetup();
 
         // Keyboard related setup, before nav bars are created.
         mHideNavBarForKeyboard = mContext.getResources().getBoolean(
@@ -374,6 +384,10 @@
         mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
         mScreenLifecycle.addObserver(mScreenObserver);
 
+        // Need to initialize HVAC controller before calling super.start - before system bars are
+        // created.
+        mHvacController = new HvacController(mContext);
+
         super.start();
         mTaskStackListener = new TaskStackListenerImpl();
         mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
@@ -392,25 +406,18 @@
 
         mHvacController.connectToCarService();
 
-        CarSystemUIFactory factory = SystemUIFactory.getInstance();
-        if (!mDeviceIsProvisioned) {
-            mDeviceProvisionedController.addCallback(
-                    new DeviceProvisionedController.DeviceProvisionedListener() {
-                        @Override
-                        public void onDeviceProvisionedChanged() {
-                            mHandler.post(() -> {
-                                // on initial boot we are getting a call even though the value
-                                // is the same so we are confirming the reset is needed
-                                boolean deviceProvisioned =
-                                        mDeviceProvisionedController.isDeviceProvisioned();
-                                if (mDeviceIsProvisioned != deviceProvisioned) {
-                                    mDeviceIsProvisioned = deviceProvisioned;
-                                    restartNavBars();
-                                }
-                            });
-                        }
-                    });
-        }
+        mDeviceProvisionedController.addCallback(
+                new DeviceProvisionedController.DeviceProvisionedListener() {
+                    @Override
+                    public void onUserSetupChanged() {
+                        mHandler.post(() -> restartNavBarsIfNecessary());
+                    }
+
+                    @Override
+                    public void onUserSwitched() {
+                        mHandler.post(() -> restartNavBarsIfNecessary());
+                    }
+                });
 
         // Register a listener for driving state changes.
         mDrivingStateHelper = new DrivingStateHelper(mContext, this::onDrivingStateChanged);
@@ -422,6 +429,14 @@
         mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
     }
 
+    private void restartNavBarsIfNecessary() {
+        boolean currentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
+        if (mDeviceIsSetUpForUser != currentUserSetup) {
+            mDeviceIsSetUpForUser = currentUserSetup;
+            restartNavBars();
+        }
+    }
+
     /**
      * Remove all content from navbars and rebuild them. Used to allow for different nav bars
      * before and after the device is provisioned. . Also for change of density and font size.
@@ -430,8 +445,8 @@
         // remove and reattach all hvac components such that we don't keep a reference to unused
         // ui elements
         mHvacController.removeAllComponents();
-        addTemperatureViewToController(mStatusBarWindow);
         mCarFacetButtonController.removeAll();
+
         if (mNavigationBarWindow != null) {
             mNavigationBarWindow.removeAllViews();
             mNavigationBarView = null;
@@ -525,7 +540,6 @@
     @Override
     protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
         super.makeStatusBarView(result);
-        mHvacController = new HvacController(mContext);
 
         CarSystemUIFactory factory = SystemUIFactory.getInstance();
         mCarFacetButtonController = factory.getCarDependencyComponent()
@@ -550,7 +564,8 @@
      * touch listeners needed for opening and closing the notification panel
      */
     private void connectNotificationsUI() {
-        // Attached to the status bar to detect pull down of the notification shade.
+        // Attached to the top navigation bar (i.e. status bar) to detect pull down of the
+        // notification shade.
         GestureDetector openGestureDetector = new GestureDetector(mContext,
                 new OpenNotificationGestureListener() {
                     @Override
@@ -583,6 +598,18 @@
         GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
                 new HandleBarCloseNotificationGestureListener());
 
+        mTopNavBarNotificationTouchListener = (v, event) -> {
+            if (!mDeviceIsSetUpForUser) {
+                return true;
+            }
+            boolean consumed = openGestureDetector.onTouchEvent(event);
+            if (consumed) {
+                return true;
+            }
+            maybeCompleteAnimation(event);
+            return true;
+        };
+
         mNavBarNotificationTouchListener =
                 (v, event) -> {
                     boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
@@ -593,21 +620,6 @@
                     return true;
                 };
 
-        // The following are the ui elements that the user would call the status bar.
-        // This will set the status bar so it they can make call backs.
-        CarNavigationBarView topBar = mStatusBarWindow.findViewById(R.id.car_top_bar);
-        topBar.setStatusBar(this);
-        topBar.setStatusBarWindowTouchListener((v1, event1) -> {
-
-                    boolean consumed = openGestureDetector.onTouchEvent(event1);
-                    if (consumed) {
-                        return true;
-                    }
-                    maybeCompleteAnimation(event1);
-                    return true;
-                }
-        );
-
         mNotificationClickHandlerFactory = new NotificationClickHandlerFactory(mBarService);
         mNotificationClickHandlerFactory.registerClickListener((launchResult, alertEntry) -> {
             if (launchResult == ActivityManager.START_TASK_TO_FRONT
@@ -914,23 +926,30 @@
     }
 
     private void buildNavBarContent() {
+        // Always build top bar.
+        buildTopBar((mDeviceIsSetUpForUser) ? R.layout.car_top_navigation_bar :
+                R.layout.car_top_navigation_bar_unprovisioned);
+
         if (mShowBottom) {
-            buildBottomBar((mDeviceIsProvisioned) ? R.layout.car_navigation_bar :
+            buildBottomBar((mDeviceIsSetUpForUser) ? R.layout.car_navigation_bar :
                     R.layout.car_navigation_bar_unprovisioned);
         }
 
         if (mShowLeft) {
-            buildLeft((mDeviceIsProvisioned) ? R.layout.car_left_navigation_bar :
+            buildLeft((mDeviceIsSetUpForUser) ? R.layout.car_left_navigation_bar :
                     R.layout.car_left_navigation_bar_unprovisioned);
         }
 
         if (mShowRight) {
-            buildRight((mDeviceIsProvisioned) ? R.layout.car_right_navigation_bar :
+            buildRight((mDeviceIsSetUpForUser) ? R.layout.car_right_navigation_bar :
                     R.layout.car_right_navigation_bar_unprovisioned);
         }
     }
 
     private void buildNavBarWindows() {
+        mTopNavigationBarContainer = mStatusBarWindow
+            .findViewById(R.id.car_top_navigation_bar_container);
+
         if (mShowBottom) {
             mNavigationBarWindow = (ViewGroup) View.inflate(mContext,
                     R.layout.navigation_bar_window, null);
@@ -963,15 +982,25 @@
         }
 
         boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
-        if (!isKeyboardVisible) {
-            attachBottomNavBarWindow();
-        } else {
-            detachBottomNavBarWindow();
-        }
+        showBottomNavBarWindow(isKeyboardVisible);
     }
 
     private void attachNavBarWindows() {
-        attachBottomNavBarWindow();
+        if (mShowBottom && !mBottomNavBarVisible) {
+            mBottomNavBarVisible = true;
+
+            WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
+                    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
+                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                            | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                            | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+                    PixelFormat.TRANSLUCENT);
+            lp.setTitle("CarNavigationBar");
+            lp.windowAnimations = 0;
+            mWindowManager.addView(mNavigationBarWindow, lp);
+        }
 
         if (mShowLeft) {
             int width = mContext.getResources().getDimensionPixelSize(
@@ -1009,47 +1038,32 @@
         }
     }
 
-    /**
-     * Attaches the bottom nav bar window. Can be extended to modify the specific behavior of
-     * attaching the bottom nav bar.
-     */
-    protected void attachBottomNavBarWindow() {
+    private void showBottomNavBarWindow(boolean isKeyboardVisible) {
         if (!mShowBottom) {
             return;
         }
 
-        if (mBottomNavBarVisible) {
+        // If keyboard is visible and bottom nav bar not visible, this is the correct state, so do
+        // nothing. Same with if keyboard is not visible and bottom nav bar is visible.
+        if (isKeyboardVisible ^ mBottomNavBarVisible) {
             return;
         }
-        mBottomNavBarVisible = true;
 
-        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
-                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
-                PixelFormat.TRANSLUCENT);
-        lp.setTitle("CarNavigationBar");
-        lp.windowAnimations = 0;
-        mWindowManager.addView(mNavigationBarWindow, lp);
+        mNavigationBarWindow.setVisibility(isKeyboardVisible ? View.GONE : View.VISIBLE);
+        mBottomNavBarVisible = !isKeyboardVisible;
     }
 
-    /**
-     * Detaches the bottom nav bar window. Can be extended to modify the specific behavior of
-     * detaching the bottom nav bar.
-     */
-    protected void detachBottomNavBarWindow() {
-        if (!mShowBottom) {
-            return;
+    private void buildTopBar(int layout) {
+        mTopNavigationBarContainer.removeAllViews();
+        View.inflate(mContext, layout, mTopNavigationBarContainer);
+        mTopNavigationBarView = (CarNavigationBarView) mTopNavigationBarContainer.getChildAt(0);
+        if (mTopNavigationBarView == null) {
+            Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_top_navigation_bar");
+            throw new RuntimeException("Unable to build top nav bar due to missing layout");
         }
-
-        if (!mBottomNavBarVisible) {
-            return;
-        }
-        mBottomNavBarVisible = false;
-        mWindowManager.removeView(mNavigationBarWindow);
+        mTopNavigationBarView.setStatusBar(this);
+        addTemperatureViewToController(mTopNavigationBarView);
+        mTopNavigationBarView.setStatusBarWindowTouchListener(mTopNavBarNotificationTouchListener);
     }
 
     private void buildBottomBar(int layout) {
@@ -1060,7 +1074,7 @@
         mNavigationBarView = (CarNavigationBarView) mNavigationBarWindow.getChildAt(0);
         if (mNavigationBarView == null) {
             Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
-            throw new RuntimeException("Unable to build botom nav bar due to missing layout");
+            throw new RuntimeException("Unable to build bottom nav bar due to missing layout");
         }
         mNavigationBarView.setStatusBar(this);
         addTemperatureViewToController(mNavigationBarView);
@@ -1071,7 +1085,7 @@
         View.inflate(mContext, layout, mLeftNavigationBarWindow);
         mLeftNavigationBarView = (CarNavigationBarView) mLeftNavigationBarWindow.getChildAt(0);
         if (mLeftNavigationBarView == null) {
-            Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
+            Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_left_navigation_bar");
             throw new RuntimeException("Unable to build left nav bar due to missing layout");
         }
         mLeftNavigationBarView.setStatusBar(this);
@@ -1084,7 +1098,7 @@
         View.inflate(mContext, layout, mRightNavigationBarWindow);
         mRightNavigationBarView = (CarNavigationBarView) mRightNavigationBarWindow.getChildAt(0);
         if (mRightNavigationBarView == null) {
-            Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
+            Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_right_navigation_bar");
             throw new RuntimeException("Unable to build right nav bar due to missing layout");
         }
         mRightNavigationBarView.setStatusBar(this);
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index 886162f..f7e5d01 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -34,6 +34,7 @@
 import android.graphics.Rect;
 import android.os.AsyncTask;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -52,6 +53,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Displays a GridLayout with icons for the users in the system to allow switching between users.
@@ -61,6 +64,7 @@
     private UserSelectionListener mUserSelectionListener;
     private UserAdapter mAdapter;
     private CarUserManagerHelper mCarUserManagerHelper;
+    private UserManager mUserManager;
     private Context mContext;
 
     private final BroadcastReceiver mUserUpdateReceiver = new BroadcastReceiver() {
@@ -74,6 +78,7 @@
         super(context, attrs);
         mContext = context;
         mCarUserManagerHelper = new CarUserManagerHelper(mContext);
+        mUserManager = UserManager.get(mContext);
 
         addItemDecoration(new ItemSpacingDecoration(context.getResources().getDimensionPixelSize(
                 R.dimen.car_user_switcher_vertical_spacing_between_users)));
@@ -103,12 +108,23 @@
      * @return the adapter
      */
     public void buildAdapter() {
-        List<UserRecord> userRecords = createUserRecords(mCarUserManagerHelper
-                .getAllUsers());
+        List<UserRecord> userRecords = createUserRecords(getAllUsers());
         mAdapter = new UserAdapter(mContext, userRecords);
         super.setAdapter(mAdapter);
     }
 
+    private List<UserInfo> getAllUsers() {
+        Stream<UserInfo> userListStream =
+                mUserManager.getUsers(/* excludeDying= */ true).stream();
+
+        if (UserManager.isHeadlessSystemUserMode()) {
+            userListStream =
+                    userListStream.filter(userInfo -> userInfo.id != UserHandle.USER_SYSTEM);
+        }
+        userListStream = userListStream.filter(userInfo -> userInfo.supportsSwitchToByUser());
+        return userListStream.collect(Collectors.toList());
+    }
+
     private List<UserRecord> createUserRecords(List<UserInfo> userInfoList) {
         List<UserRecord> userRecords = new ArrayList<>();
 
@@ -173,7 +189,7 @@
 
     private void onUsersUpdate() {
         mAdapter.clearUsers();
-        mAdapter.updateUsers(createUserRecords(mCarUserManagerHelper.getAllUsers()));
+        mAdapter.updateUsers(createUserRecords(getAllUsers()));
         mAdapter.notifyDataSetChanged();
     }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java
index ead1de2..88d641e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java
@@ -47,6 +47,7 @@
 public class OngoingPrivacyChip extends LinearLayout implements View.OnClickListener {
 
     private Context mContext;
+    private Handler mHandler;
 
     private LinearLayout mIconsContainer;
     private List<PrivacyItem> mPrivacyItems;
@@ -88,6 +89,7 @@
 
     private void init(Context context) {
         mContext = context;
+        mHandler = new Handler(Looper.getMainLooper());
         mPrivacyItems = new ArrayList<>();
         sAppOpsController = Dependency.get(AppOpsController.class);
         mUserManager = mContext.getSystemService(UserManager.class);
@@ -131,8 +133,7 @@
     @Override
     public void onClick(View v) {
         updatePrivacyList();
-        Handler mUiHandler = new Handler(Looper.getMainLooper());
-        mUiHandler.post(() -> {
+        mHandler.post(() -> {
             mActivityStarter.postStartActivityDismissingKeyguard(
                     new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0);
         });
@@ -152,21 +153,17 @@
     }
 
     private void updatePrivacyList() {
-        mPrivacyItems = mCurrentUserIds.stream()
+        List<PrivacyItem> privacyItems = mCurrentUserIds.stream()
                 .flatMap(item -> sAppOpsController.getActiveAppOpsForUser(item).stream())
                 .filter(Objects::nonNull)
                 .map(item -> toPrivacyItem(item))
                 .filter(Objects::nonNull)
                 .collect(Collectors.toList());
-        mPrivacyDialogBuilder = new PrivacyDialogBuilder(mContext, mPrivacyItems);
-
-        Handler refresh = new Handler(Looper.getMainLooper());
-        refresh.post(new Runnable() {
-            @Override
-            public void run() {
-                updateView();
-            }
-        });
+        if (!privacyItems.equals(mPrivacyItems)) {
+            mPrivacyItems = privacyItems;
+            mPrivacyDialogBuilder = new PrivacyDialogBuilder(mContext, mPrivacyItems);
+            mHandler.post(this::updateView);
+        }
     }
 
     private PrivacyItem toPrivacyItem(AppOpItem appOpItem) {
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java
index 5ec7a77..820bea9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java
@@ -23,16 +23,20 @@
 import android.graphics.drawable.Drawable;
 import android.util.Log;
 
+import java.util.Objects;
+
 /**
  * Class to hold the data for the applications that are using the AppOps permissions.
  */
 public class PrivacyApplication {
     private static final String TAG = "PrivacyApplication";
 
+    private String mPackageName;
     private Drawable mIcon;
     private String mApplicationName;
 
     public PrivacyApplication(String packageName, Context context) {
+        mPackageName = packageName;
         try {
             CarUserManagerHelper carUserManagerHelper = new CarUserManagerHelper(context);
             ApplicationInfo app = context.getPackageManager()
@@ -59,4 +63,17 @@
     public String getApplicationName() {
         return mApplicationName;
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PrivacyApplication that = (PrivacyApplication) o;
+        return mPackageName.equals(that.mPackageName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPackageName);
+    }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java
index fca1373..d3e123e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.car.privacy;
 
+import java.util.Objects;
+
 /**
  * Class for holding the data of each privacy item displayed in {@link OngoingPrivacyDialog}
  */
@@ -43,4 +45,18 @@
     public PrivacyType getPrivacyType() {
         return mPrivacyType;
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PrivacyItem that = (PrivacyItem) o;
+        return mPrivacyType == that.mPrivacyType
+                && mPrivacyApplication.equals(that.mPrivacyApplication);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPrivacyType, mPrivacyApplication);
+    }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
index 71cc19b..4d6af95 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
@@ -19,15 +19,21 @@
 import android.content.Context;
 
 import com.android.systemui.SystemUI;
+import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.VolumeDialog;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 /**
  * Allows for adding car specific dialog when the volume dialog is created.
  */
+@Singleton
 public class CarVolumeDialogComponent extends VolumeDialogComponent {
 
-    public CarVolumeDialogComponent(SystemUI sysui, Context context) {
-        super(sysui, context);
+    @Inject
+    public CarVolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator) {
+        super(context, keyguardViewMediator);
     }
 
     protected VolumeDialog createDefault() {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index 81c5bcd..642dc82 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -35,7 +35,6 @@
 import android.net.http.SslError;
 import android.os.Bundle;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -477,11 +476,11 @@
     }
 
     private static void logd(String s) {
-        Rlog.d(TAG, s);
+        Log.d(TAG, s);
     }
 
     private static void loge(String s) {
-        Rlog.d(TAG, s);
+        Log.d(TAG, s);
     }
 
 }
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
index 02c61d7..46b1d5f 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
@@ -19,7 +19,6 @@
 import android.content.Intent;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -27,7 +26,6 @@
 import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 
 /**
@@ -44,7 +42,7 @@
     private static final String INTER_GROUP_DELIMITER = "\\s*:\\s*";
 
     private static final String TAG = CustomConfigLoader.class.getSimpleName();
-    private static final boolean VDBG = Rlog.isLoggable(TAG, Log.VERBOSE);
+    private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
 
     /**
      * loads and parses the carrier config, return a list of carrier action for the given signal
@@ -70,7 +68,7 @@
         // return an empty list if no match found
         List<Integer> actionList = new ArrayList<>();
         if (carrierConfigManager == null) {
-            Rlog.e(TAG, "load carrier config failure with carrier config manager uninitialized");
+            Log.e(TAG, "load carrier config failure with carrier config manager uninitialized");
             return actionList;
         }
         PersistableBundle b = carrierConfigManager.getConfig();
@@ -101,8 +99,8 @@
                             .EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false));
                     break;
                 default:
-                    Rlog.e(TAG, "load carrier config failure with un-configured key: " +
-                            intent.getAction());
+                    Log.e(TAG, "load carrier config failure with un-configured key: "
+                            + intent.getAction());
                     break;
             }
             if (!ArrayUtils.isEmpty(configs)) {
@@ -111,12 +109,12 @@
                     matchConfig(config, arg1, arg2, actionList);
                     if (!actionList.isEmpty()) {
                         // return the first match
-                        if (VDBG) Rlog.d(TAG, "found match action list: " + actionList.toString());
+                        if (VDBG) Log.d(TAG, "found match action list: " + actionList.toString());
                         return actionList;
                     }
                 }
             }
-            Rlog.d(TAG, "no matching entry for signal: " + intent.getAction() + "arg1: " + arg1
+            Log.d(TAG, "no matching entry for signal: " + intent.getAction() + "arg1: " + arg1
                     + "arg2: " + arg2);
         }
         return actionList;
@@ -166,7 +164,7 @@
                 try {
                     actionList.add(Integer.parseInt(idx));
                 } catch (NumberFormatException e) {
-                    Rlog.e(TAG, "NumberFormatException(string: " + idx + " config:" + config + "): "
+                    Log.e(TAG, "NumberFormatException(string: " + idx + " config:" + config + "): "
                             + e);
                 }
             }
diff --git a/packages/EncryptedLocalTransport/Android.bp b/packages/EncryptedLocalTransport/Android.bp
new file mode 100644
index 0000000..dd30ad1
--- /dev/null
+++ b/packages/EncryptedLocalTransport/Android.bp
@@ -0,0 +1,27 @@
+//
+// 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.
+//
+
+android_app {
+    name: "EncryptedLocalTransport",
+    srcs: ["src/**/*.java"],
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+    },
+    static_libs: ["LocalTransport"],
+    platform_apis: true,
+    certificate: "platform",
+    privileged: true,
+}
diff --git a/packages/EncryptedLocalTransport/AndroidManifest.xml b/packages/EncryptedLocalTransport/AndroidManifest.xml
new file mode 100644
index 0000000..dc3617f
--- /dev/null
+++ b/packages/EncryptedLocalTransport/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2019 Google Inc.
+ *
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.android.encryptedlocaltransport"
+      android:sharedUserId="android.uid.system" >
+
+
+    <application android:allowBackup="false" >
+        <!-- This service does not need to be exported because it shares uid with the system server
+        which is the only client. -->
+        <service android:name=".EncryptedLocalTransportService"
+                 android:permission="android.permission.CONFIRM_FULL_BACKUP"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="android.backup.TRANSPORT_HOST" />
+            </intent-filter>
+        </service>
+
+    </application>
+</manifest>
diff --git a/packages/EncryptedLocalTransport/proguard.flags b/packages/EncryptedLocalTransport/proguard.flags
new file mode 100644
index 0000000..e4ce3c5
--- /dev/null
+++ b/packages/EncryptedLocalTransport/proguard.flags
@@ -0,0 +1,2 @@
+-keep class com.android.localTransport.EncryptedLocalTransport
+-keep class com.android.localTransport.EncryptedLocalTransportService
diff --git a/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransport.java b/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransport.java
new file mode 100644
index 0000000..3dd453e
--- /dev/null
+++ b/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransport.java
@@ -0,0 +1,109 @@
+/*
+ * 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.encryptedlocaltransport;
+
+import android.app.backup.BackupTransport;
+import android.app.backup.RestoreDescription;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
+import android.util.Log;
+
+import com.android.localtransport.LocalTransport;
+import com.android.localtransport.LocalTransportParameters;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+
+public class EncryptedLocalTransport extends LocalTransport {
+    private static final String TAG = "EncryptedLocalTransport";
+    private static final int BACKUP_BUFFER_SIZE = 32 * 1024; // 32 KB.
+
+    public EncryptedLocalTransport(Context context,
+            LocalTransportParameters parameters) {
+        super(context, parameters);
+    }
+
+    @Override
+    public int performBackup(
+            PackageInfo packageInfo, ParcelFileDescriptor data, int flags) {
+        File packageFile;
+        try {
+            StructStat stat = Os.fstat(data.getFileDescriptor());
+            if (stat.st_size > KEY_VALUE_BACKUP_SIZE_QUOTA) {
+                Log.w(TAG, "New datastore size " + stat.st_size
+                        + " exceeds quota " + KEY_VALUE_BACKUP_SIZE_QUOTA);
+                return TRANSPORT_QUOTA_EXCEEDED;
+            }
+        } catch (ErrnoException e) {
+            Log.w(TAG, "Failed to stat the backup input file: ", e);
+            return BackupTransport.TRANSPORT_ERROR;
+        }
+
+        clearBackupData(packageInfo);
+
+        try (InputStream in = new FileInputStream(data.getFileDescriptor())) {
+            packageFile = new File(mCurrentSetIncrementalDir, packageInfo.packageName);
+            Files.copy(in, packageFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to save backup data to file: ", e);
+            return BackupTransport.TRANSPORT_ERROR;
+        }
+
+        return TRANSPORT_OK;
+    }
+
+    @Override
+    public int getRestoreData(ParcelFileDescriptor outFd) {
+        if (mRestorePackages == null) {
+            throw new IllegalStateException("startRestore not called");
+        }
+        if (mRestorePackage < 0) {
+            throw new IllegalStateException("nextRestorePackage not called");
+        }
+        if (mRestoreType != RestoreDescription.TYPE_KEY_VALUE) {
+            throw new IllegalStateException("getRestoreData(fd) for non-key/value dataset");
+        }
+
+        try(OutputStream out = new FileOutputStream(outFd.getFileDescriptor())) {
+            File packageFile = new File(mRestoreSetIncrementalDir,
+                    mRestorePackages[mRestorePackage].packageName);
+            Files.copy(packageFile.toPath(), out);
+        } catch (IOException e) {
+            Log.d(TAG, "Failed to transfer restore data: " + e);
+            return BackupTransport.TRANSPORT_ERROR;
+        }
+
+        return BackupTransport.TRANSPORT_OK;
+    }
+
+    @Override
+    protected boolean hasRestoreDataForPackage(String packageName) {
+        File contents = (new File(mRestoreSetIncrementalDir, packageName));
+        return contents.exists() && contents.length() != 0;
+
+    }
+}
diff --git a/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransportService.java b/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransportService.java
new file mode 100644
index 0000000..952f90d
--- /dev/null
+++ b/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransportService.java
@@ -0,0 +1,47 @@
+/*
+ * 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.encryptedlocaltransport;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+import com.android.localtransport.LocalTransportParameters;
+
+public class EncryptedLocalTransportService extends Service {
+    private static EncryptedLocalTransport sTransport = null;
+
+    @Override
+    public void onCreate() {
+        if (sTransport == null) {
+            LocalTransportParameters parameters =
+                    new LocalTransportParameters(getMainThreadHandler(), getContentResolver());
+            sTransport = new EncryptedLocalTransport(this, parameters);
+        }
+        sTransport.getParameters().start();
+    }
+
+    @Override
+    public void onDestroy() {
+        sTransport.getParameters().stop();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return sTransport.getBinder();
+    }
+}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 1b27b52..48d34ae 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.externalstorage;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.usage.StorageStatsManager;
 import android.content.ContentResolver;
@@ -298,6 +299,53 @@
         return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
     }
 
+    /**
+     * Check that the directory is the root of storage or blocked file from tree.
+     *
+     * @param docId the docId of the directory to be checked
+     * @return true, should be blocked from tree. Otherwise, false.
+     */
+    @Override
+    protected boolean shouldBlockFromTree(@NonNull String docId) {
+        try {
+            final File dir = getFileForDocId(docId, true /* visible */).getCanonicalFile();
+            if (!dir.isDirectory()) {
+                return false;
+            }
+
+            final String path = dir.getAbsolutePath();
+
+            // Block Download folder from tree
+            if (MediaStore.Downloads.isDownloadDir(path)) {
+                return true;
+            }
+
+            final ArrayMap<String, RootInfo> roots = new ArrayMap<>();
+
+            synchronized (mRootsLock) {
+                roots.putAll(mRoots);
+            }
+
+            // block root of storage
+            for (int i = 0; i < roots.size(); i++) {
+                RootInfo rootInfo = roots.valueAt(i);
+                // skip home root
+                if (TextUtils.equals(rootInfo.rootId, ROOT_ID_HOME)) {
+                    continue;
+                }
+
+                // block the root of storage
+                if (TextUtils.equals(path, rootInfo.visiblePath.getAbsolutePath())) {
+                    return true;
+                }
+            }
+            return false;
+        } catch (IOException e) {
+            throw new IllegalArgumentException(
+                    "Failed to determine if " + docId + " should block from tree " + ": " + e);
+        }
+    }
+
     @Override
     protected String getDocIdForFile(File file) throws FileNotFoundException {
         return getDocIdForFileMaybeCreate(file, false);
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 4408ef5..50f858e 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -16,6 +16,7 @@
 
 package com.android.localtransport;
 
+import android.annotation.Nullable;
 import android.app.backup.BackupAgent;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
@@ -71,19 +72,19 @@
 
     // Size quotas at reasonable values, similar to the current cloud-storage limits
     private static final long FULL_BACKUP_SIZE_QUOTA = 25 * 1024 * 1024;
-    private static final long KEY_VALUE_BACKUP_SIZE_QUOTA = 5 * 1024 * 1024;
+    protected static final long KEY_VALUE_BACKUP_SIZE_QUOTA = 5 * 1024 * 1024;
 
     private Context mContext;
     private File mDataDir;
     private File mCurrentSetDir;
-    private File mCurrentSetIncrementalDir;
+    protected File mCurrentSetIncrementalDir;
     private File mCurrentSetFullDir;
 
-    private PackageInfo[] mRestorePackages = null;
-    private int mRestorePackage = -1;  // Index into mRestorePackages
-    private int mRestoreType;
+    protected PackageInfo[] mRestorePackages = null;
+    protected int mRestorePackage = -1;  // Index into mRestorePackages
+    protected int mRestoreType;
     private File mRestoreSetDir;
-    private File mRestoreSetIncrementalDir;
+    protected File mRestoreSetIncrementalDir;
     private File mRestoreSetFullDir;
 
     // Additional bookkeeping for full backup
@@ -115,7 +116,7 @@
         makeDataDirs();
     }
 
-    LocalTransportParameters getParameters() {
+    public LocalTransportParameters getParameters() {
         return mParameters;
     }
 
@@ -142,11 +143,18 @@
         return null;
     }
 
+    /** @removed Replaced with dataManagementIntentLabel in the API */
     public String dataManagementLabel() {
         return TRANSPORT_DATA_MANAGEMENT_LABEL;
     }
 
     @Override
+    @Nullable
+    public CharSequence dataManagementIntentLabel() {
+        return TRANSPORT_DATA_MANAGEMENT_LABEL;
+    }
+
+    @Override
     public String transportDirName() {
         return TRANSPORT_DIR_NAME;
     }
@@ -537,14 +545,14 @@
         int bytesLeft = numBytes;
         while (bytesLeft > 0) {
             try {
-            int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft);
-            if (nRead < 0) {
-                // Something went wrong if we expect data but saw EOD
-                Log.w(TAG, "Unexpected EOD; failing backup");
-                return TRANSPORT_ERROR;
-            }
-            mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
-            bytesLeft -= nRead;
+                int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft);
+                if (nRead < 0) {
+                    // Something went wrong if we expect data but saw EOD
+                    Log.w(TAG, "Unexpected EOD; failing backup");
+                    return TRANSPORT_ERROR;
+                }
+                mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
+                bytesLeft -= nRead;
             } catch (IOException e) {
                 Log.e(TAG, "Error handling backup data for " + mFullTargetPackage);
                 return TRANSPORT_ERROR;
@@ -620,20 +628,15 @@
         }
         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
 
-        boolean found = false;
+        boolean found;
         while (++mRestorePackage < mRestorePackages.length) {
             String name = mRestorePackages[mRestorePackage].packageName;
 
             // If we have key/value data for this package, deliver that
             // skip packages where we have a data dir but no actual contents
-            String[] contents = (new File(mRestoreSetIncrementalDir, name)).list();
-            if (contents != null && contents.length > 0) {
-                if (DEBUG) {
-                    Log.v(TAG, "  nextRestorePackage(TYPE_KEY_VALUE) @ "
-                        + mRestorePackage + " = " + name);
-                }
+            found = hasRestoreDataForPackage(name);
+            if (found) {
                 mRestoreType = RestoreDescription.TYPE_KEY_VALUE;
-                found = true;
             }
 
             if (!found) {
@@ -664,6 +667,18 @@
         return RestoreDescription.NO_MORE_PACKAGES;
     }
 
+    protected boolean hasRestoreDataForPackage(String packageName) {
+        String[] contents = (new File(mRestoreSetIncrementalDir, packageName)).list();
+        if (contents != null && contents.length > 0) {
+            if (DEBUG) {
+                Log.v(TAG, "  nextRestorePackage(TYPE_KEY_VALUE) @ "
+                        + mRestorePackage + " = " + packageName);
+            }
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public int getRestoreData(ParcelFileDescriptor outFd) {
         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
index 784be22..8b4db92 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
@@ -22,7 +22,7 @@
 import android.provider.Settings;
 import android.util.KeyValueListParser;
 
-class LocalTransportParameters extends KeyValueSettingObserver {
+public class LocalTransportParameters extends KeyValueSettingObserver {
     private static final String TAG = "LocalTransportParams";
     private static final String SETTING = Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS;
     private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag";
@@ -31,7 +31,7 @@
     private boolean mFakeEncryptionFlag;
     private boolean mIsNonIncrementalOnly;
 
-    LocalTransportParameters(Handler handler, ContentResolver resolver) {
+    public LocalTransportParameters(Handler handler, ContentResolver resolver) {
         super(handler, resolver, Settings.Secure.getUriFor(SETTING));
     }
 
diff --git a/packages/PackageInstaller/res/values-television/themes.xml b/packages/PackageInstaller/res/values-television/themes.xml
new file mode 100644
index 0000000..5ae4957
--- /dev/null
+++ b/packages/PackageInstaller/res/values-television/themes.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<resources>
+
+    <style name="Theme.AlertDialogActivity.NoAnimation">
+        <item name="android:windowAnimationStyle">@null</item>
+    </style>
+
+    <style name="Theme.AlertDialogActivity"
+           parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+
+    <style name="Theme.AlertDialogActivity.NoActionBar"
+           parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
+    </style>
+
+</resources>
diff --git a/packages/PrintSpooler/TEST_MAPPING b/packages/PrintSpooler/TEST_MAPPING
new file mode 100644
index 0000000..4fa8822
--- /dev/null
+++ b/packages/PrintSpooler/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsPrintTestCases",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        }
+      ]
+    }
+  ]
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/OWNERS b/packages/SettingsProvider/src/android/provider/settings/OWNERS
new file mode 100644
index 0000000..541dd878
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/OWNERS
@@ -0,0 +1,5 @@
+# Please reach out to Android B&R when making Settings backup changes
+alsutton@google.com
+nathch@google.com
+rthakohov@google.com
+
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java
new file mode 100644
index 0000000..e425790
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java
@@ -0,0 +1,36 @@
+/*
+ * 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.provider.settings.backup;
+
+import android.provider.Settings;
+
+/** Device specific settings list */
+public class DeviceSpecificSettings {
+    /**
+     * The settings values which should only be restored if the target device is the
+     * same as the source device
+     *
+     * NOTE: Settings are backed up and restored in the order they appear
+     *       in this array. If you have one setting depending on another,
+     *       make sure that they are ordered appropriately.
+     *
+     * @hide
+     */
+    public static final String[] DEVICE_SPECIFIC_SETTINGS_TO_BACKUP = {
+            Settings.Secure.DISPLAY_DENSITY_FORCED,
+    };
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 976f336..ef67bbd 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -41,8 +41,8 @@
 public class SecureSettingsValidators {
     /**
      * All settings in {@link Secure.SETTINGS_TO_BACKUP} and {@link
-     * Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP} array *must* have a non-null validator, otherwise
-     * they won't be restored.
+     * DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP} array *must* have a non-null
+     * validator, otherwise they won't be restored.
      */
     public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 5e2b7c8..17c621e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -51,6 +51,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockscreenCredential;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -1992,8 +1993,11 @@
                 try {
                     LockPatternUtils lpu = new LockPatternUtils(mContext);
                     List<LockPatternView.Cell> cellPattern =
-                            LockPatternUtils.stringToPattern(lockPattern);
-                    lpu.saveLockPattern(cellPattern, null, UserHandle.USER_SYSTEM);
+                            LockPatternUtils.byteArrayToPattern(lockPattern.getBytes());
+                    lpu.setLockCredential(
+                            LockscreenCredential.createPattern(cellPattern),
+                            LockscreenCredential.createNone(),
+                            UserHandle.USER_SYSTEM);
                 } catch (IllegalArgumentException e) {
                     // Don't want corrupted lock pattern to hang the reboot process
                 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index f545fa6..7e60452 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -34,6 +34,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.settings.backup.DeviceSpecificSettings;
 import android.provider.settings.backup.GlobalSettings;
 import android.provider.settings.backup.SecureSettings;
 import android.provider.settings.backup.SystemSettings;
@@ -641,7 +642,7 @@
         if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
             whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP,
                     Settings.Secure.LEGACY_RESTORE_SETTINGS,
-                    Settings.Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
+                    DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
             validators = SecureSettingsValidators.VALIDATORS;
         } else if (contentUri.equals(Settings.System.CONTENT_URI)) {
             whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP,
@@ -1000,7 +1001,7 @@
                      getContentResolver()
                              .query(Settings.Secure.CONTENT_URI, PROJECTION, null, null, null)) {
             return extractRelevantValues(
-                    cursor, Settings.Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
+                    cursor, DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
         }
     }
 
diff --git a/packages/SettingsProvider/test/src/android/provider/OWNERS b/packages/SettingsProvider/test/src/android/provider/OWNERS
new file mode 100644
index 0000000..f3241ea
--- /dev/null
+++ b/packages/SettingsProvider/test/src/android/provider/OWNERS
@@ -0,0 +1,4 @@
+per-file * = *
+
+# Please reach out to the Android B&R team for settings backup changes
+per-file SettingsBackupTest.java = alsutton@google.com, nathch@google.com, rthakohov@google.com
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 8437eae..62827bc 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -16,6 +16,8 @@
 
 package android.provider;
 
+import static android.provider.settings.backup.DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP;
+
 import static com.google.android.collect.Sets.newHashSet;
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -750,7 +752,7 @@
     public void secureSettingsBackedUpOrBlacklisted() {
         HashSet<String> keys = new HashSet<String>();
         Collections.addAll(keys, SecureSettings.SETTINGS_TO_BACKUP);
-        Collections.addAll(keys, Settings.Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
+        Collections.addAll(keys, DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
         checkSettingsBackedUpOrBlacklisted(
                 getCandidateSettings(Settings.Secure.class),
                 keys,
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index a097249..665bde3 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -476,10 +476,10 @@
     }
 
     private static void addScreenshotToIntent(Intent intent, BugreportInfo info) {
-        final String screenshotFileName = info.name + ".png";
-        final File screenshotFile = new File(BUGREPORT_DIR, screenshotFileName);
-        final String screenshotFilePath = screenshotFile.getAbsolutePath();
-        if (screenshotFile.length() > 0) {
+        final File screenshotFile = info.screenshotFiles.isEmpty()
+                ? null : info.screenshotFiles.get(0);
+        if (screenshotFile != null && screenshotFile.length() > 0) {
+            final String screenshotFilePath = screenshotFile.getAbsolutePath();
             intent.putExtra(EXTRA_SCREENSHOT, screenshotFilePath);
         }
         return;
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 83acfa0..656827a 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -46,3 +46,6 @@
 #Android Auto
 stenning@google.com
 
+#Android TV
+rgl@google.com
+
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java
index cac673f..c1d4b03 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java
@@ -33,4 +33,9 @@
       * will add home controls to this space.
       */
     void sendParentGroup(ViewGroup group);
+
+    /**
+     * When visible, will poll for updates.
+     */
+    void setVisible(boolean visible);
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index db026ca..6518924 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -186,6 +186,8 @@
             return toStringBuilder().toString();
         }
 
+        // Used in dumps to determine current state of a tile.
+        // This string may be used for CTS testing of tiles, so removing elements is discouraged.
         protected StringBuilder toStringBuilder() {
             final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
             sb.append(",icon=").append(icon);
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 22b0ab7..3e74970 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -35,3 +35,7 @@
     *;
 }
 -keep class androidx.core.app.CoreComponentFactory
+
+-keep public class * extends com.android.systemui.SystemUI {
+    public <init>(android.content.Context);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_lightbulb_outline_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_lightbulb_outline_gm2_24px.xml
new file mode 100644
index 0000000..87684a3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_lightbulb_outline_gm2_24px.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M9,21c0,0.55 0.45,1 1,1h4c0.55,0 1,-0.45 1,-1v-1L9,20v1zM12,2C8.14,2 5,5.14 5,9c0,2.38 1.19,4.47 3,5.74L8,17c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1v-2.26c1.81,-1.27 3,-3.36 3,-5.74 0,-3.86 -3.14,-7 -7,-7zM14.85,13.1l-0.85,0.6L14,16h-4v-2.3l-0.85,-0.6C7.8,12.16 7,10.63 7,9c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.63 -0.8,3.16 -2.15,4.1z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/home_controls.xml b/packages/SystemUI/res/layout/home_controls.xml
new file mode 100644
index 0000000..bb971c2
--- /dev/null
+++ b/packages/SystemUI/res/layout/home_controls.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/home_controls_layout"
+    android:layout_width="match_parent"
+    android:layout_height="125dp"
+    android:layout_gravity="@integer/notification_panel_layout_gravity"
+    android:visibility="gone"
+    android:padding="8dp"
+    android:layout_margin="5dp"
+    android:background="?android:attr/colorBackgroundFloating"
+    android:orientation="vertical">
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 0e59a41..4869be1 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -51,17 +51,7 @@
             systemui:viewType="com.android.systemui.plugins.qs.QS" />
 
         <!-- Temporary area to test out home controls -->
-        <LinearLayout
-            android:id="@+id/home_controls_layout"
-            android:layout_width="match_parent"
-            android:layout_height="125dp"
-            android:layout_gravity="@integer/notification_panel_layout_gravity"
-            android:visibility="gone"
-            android:padding="8dp"
-            android:layout_margin="5dp"
-            android:background="?android:attr/colorBackgroundFloating"
-            android:orientation="vertical">
-        </LinearLayout>
+        <include layout="@layout/home_controls" />
 
         <com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
             android:id="@+id/notification_stack_scroller"
@@ -107,4 +97,4 @@
         android:background="@drawable/qs_navbar_scrim" />
 
     <include layout="@layout/status_bar_expanded_plugin_frame"/>
-</merge>
\ No newline at end of file
+</merge>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 105b27e..efcc2c4 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -117,7 +117,7 @@
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night
+        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls
     </string>
 
     <!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8335c11..3cc683a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -129,6 +129,9 @@
     <!-- Prompt for the USB device confirm dialog [CHAR LIMIT=80] -->
     <string name="usb_device_confirm_prompt">Open <xliff:g id="application">%1$s</xliff:g> to handle <xliff:g id="usb_device">%2$s</xliff:g>?</string>
 
+    <!-- Prompt for the USB device confirm dialog with warning text for USB device dialogs.  [CHAR LIMIT=200] -->
+    <string name="usb_device_confirm_prompt_warn">Open <xliff:g id="application" example= "Usb Mega Player">%1$s</xliff:g> to handle <xliff:g id="usb_device" example="USB Headphones">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device.</string>
+
     <!-- Prompt for the USB accessory confirm dialog [CHAR LIMIT=80] -->
     <string name="usb_accessory_confirm_prompt">Open <xliff:g id="application">%1$s</xliff:g> to handle <xliff:g id="usb_accessory">%2$s</xliff:g>?</string>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index 2ef0422..748f356 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -115,4 +115,15 @@
             Log.e(TAG, "Failed to clean up screenshot of recents animation", e);
         }
     }
+
+    /**
+     * @see {{@link IRecentsAnimationController#setWillFinishToHome(boolean)}}.
+     */
+    public void setWillFinishToHome(boolean willFinishToHome) {
+        try {
+            mAnimationController.setWillFinishToHome(willFinishToHome);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to set overview reached state", e);
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index ad182fe..22d1675c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -53,7 +53,6 @@
     public static final int TRANSIT_WALLPAPER_INTRA_CLOSE =
             WindowManager.TRANSIT_WALLPAPER_INTRA_CLOSE;
     public static final int TRANSIT_TASK_OPEN_BEHIND = WindowManager.TRANSIT_TASK_OPEN_BEHIND;
-    public static final int TRANSIT_TASK_IN_PLACE = WindowManager.TRANSIT_TASK_IN_PLACE;
     public static final int TRANSIT_ACTIVITY_RELAUNCH = WindowManager.TRANSIT_ACTIVITY_RELAUNCH;
     public static final int TRANSIT_DOCK_TASK_FROM_RECENTS =
             WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS;
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index ef9538d..46b4c6b 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -170,7 +170,7 @@
         mSeparator = separator;
         mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
         mSimSlotsNumber = ((TelephonyManager) context.getSystemService(
-                Context.TELEPHONY_SERVICE)).getMaxPhoneCount();
+                Context.TELEPHONY_SERVICE)).getSupportedModemCount();
         mSimErrorState = new boolean[mSimSlotsNumber];
         mMainHandler = Dependency.get(Dependency.MAIN_HANDLER);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index d45603f..ebac3d9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -33,10 +33,9 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternChecker;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.systemui.R;
 
-import java.util.Arrays;
-
 /**
  * Base class for PIN and password unlock screens.
  */
@@ -132,19 +131,20 @@
     protected void verifyPasswordAndUnlock() {
         if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
 
-        final byte[] entry = getPasswordText();
+        final LockscreenCredential password =
+                LockscreenCredential.createPassword(getPasswordText());
         setPasswordEntryInputEnabled(false);
         if (mPendingLockCheck != null) {
             mPendingLockCheck.cancel(false);
         }
 
         final int userId = KeyguardUpdateMonitor.getCurrentUser();
-        if (entry.length <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
+        if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
             // to avoid accidental lockout, only count attempts that are long enough to be a
             // real password. This may require some tweaking.
             setPasswordEntryInputEnabled(true);
             onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */);
-            Arrays.fill(entry, (byte) 0);
+            password.zeroize();
             return;
         }
 
@@ -152,9 +152,9 @@
             LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
             LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
         }
-        mPendingLockCheck = LockPatternChecker.checkPassword(
+        mPendingLockCheck = LockPatternChecker.checkCredential(
                 mLockPatternUtils,
-                entry,
+                password,
                 userId,
                 new LockPatternChecker.OnCheckCallback() {
 
@@ -166,7 +166,7 @@
                         }
                         onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
                                 true /* isValidPassword */);
-                        Arrays.fill(entry, (byte) 0);
+                        password.zeroize();
                     }
 
                     @Override
@@ -181,7 +181,7 @@
                             onPasswordChecked(userId, false /* matched */, timeoutMs,
                                     true /* isValidPassword */);
                         }
-                        Arrays.fill(entry, (byte) 0);
+                        password.zeroize();
                     }
 
                     @Override
@@ -192,7 +192,7 @@
                             LatencyTracker.getInstance(mContext).onActionEnd(
                                     ACTION_CHECK_CREDENTIAL_UNLOCKED);
                         }
-                        Arrays.fill(entry, (byte) 0);
+                        password.zeroize();
                     }
                 });
     }
@@ -223,7 +223,7 @@
     }
 
     protected abstract void resetPasswordText(boolean animate, boolean announce);
-    protected abstract byte[] getPasswordText();
+    protected abstract CharSequence getPasswordText();
     protected abstract void setPasswordEntryEnabled(boolean enabled);
     protected abstract void setPasswordEntryInputEnabled(boolean enabled);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index e3ac0f6..12c9fc9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -243,8 +243,8 @@
     }
 
     @Override
-    protected byte[] getPasswordText() {
-        return charSequenceToByteArray(mPasswordEntry.getText());
+    protected CharSequence getPasswordText() {
+        return mPasswordEntry.getText();
     }
 
     @Override
@@ -379,18 +379,4 @@
         return getContext().getString(
                 com.android.internal.R.string.keyguard_accessibility_password_unlock);
     }
-
-    /*
-     * This method avoids creating a new string when getting a byte array from EditView#getText().
-     */
-    private static byte[] charSequenceToByteArray(CharSequence chars) {
-        if (chars == null) {
-            return null;
-        }
-        byte[] bytes = new byte[chars.length()];
-        for (int i = 0; i < chars.length(); i++) {
-            bytes[i] = (byte) chars.charAt(i);
-        }
-        return bytes;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 297052f..9eb168a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -39,6 +39,7 @@
 import com.android.internal.widget.LockPatternChecker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.settingslib.animation.AppearAnimationCreator;
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
@@ -297,9 +298,9 @@
                 LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
                 LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
             }
-            mPendingLockCheck = LockPatternChecker.checkPattern(
+            mPendingLockCheck = LockPatternChecker.checkCredential(
                     mLockPatternUtils,
-                    pattern,
+                    LockscreenCredential.createPattern(pattern),
                     userId,
                     new LockPatternChecker.OnCheckCallback() {
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 274f739..8e9df55 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -167,8 +167,8 @@
     }
 
     @Override
-    protected byte[] getPasswordText() {
-        return charSequenceToByteArray(mPasswordEntry.getText());
+    protected CharSequence getPasswordText() {
+        return mPasswordEntry.getText();
     }
 
     @Override
@@ -266,18 +266,4 @@
         return getContext().getString(
                 com.android.internal.R.string.keyguard_accessibility_pin_unlock);
     }
-
-    /*
-     * This method avoids creating a new string when getting a byte array from EditView#getText().
-     */
-    private static byte[] charSequenceToByteArray(CharSequence chars) {
-        if (chars == null) {
-            return null;
-        }
-        byte[] bytes = new byte[chars.length()];
-        for (int i = 0; i < chars.length(); i++) {
-            bytes[i] = (byte) chars.charAt(i);
-        }
-        return bytes;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index af4e61b..5d35169 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -31,6 +31,7 @@
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
+import android.graphics.text.LineBreaker;
 import android.net.Uri;
 import android.os.Trace;
 import android.provider.Settings;
@@ -152,6 +153,7 @@
         mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize(
                 R.dimen.header_row_font_size);
         mTitle.setOnClickListener(this);
+        mTitle.setBreakStrategy(LineBreaker.BREAK_STRATEGY_BALANCED);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9bcccbc..ac5e255 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -99,6 +99,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.WirelessUtils;
+import com.android.systemui.DejankUtils;
 import com.android.systemui.R;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -1437,6 +1438,8 @@
     }
 
     private void handleScreenTurnedOff() {
+        final String tag = "KeyguardUpdateMonitor#handleScreenTurnedOff";
+        DejankUtils.startDetectingBlockingIpcs(tag);
         checkIsHandlerThread();
         mHardwareFingerprintUnavailableRetryCount = 0;
         mHardwareFaceUnavailableRetryCount = 0;
@@ -1446,6 +1449,7 @@
                 cb.onScreenTurnedOff();
             }
         }
+        DejankUtils.stopDetectingBlockingIpcs(tag);
     }
 
     private void handleDreamingStateChanged(int dreamStart) {
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 07bfa71..ff8a932 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -17,6 +17,7 @@
 import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.INotificationManager;
+import android.app.IWallpaperManager;
 import android.content.res.Configuration;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.display.NightDisplayListener;
@@ -77,6 +78,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
@@ -318,6 +320,8 @@
     @Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
     @Inject Lazy<AlarmManager> mAlarmManager;
     @Inject Lazy<KeyguardSecurityModel> mKeyguardSecurityModel;
+    @Inject Lazy<DozeParameters> mDozeParameters;
+    @Inject Lazy<IWallpaperManager> mWallpaperManager;
 
     @Inject
     public Dependency() {
@@ -504,6 +508,8 @@
         mProviders.put(SysUiState.class, mSysUiStateFlagsContainer::get);
         mProviders.put(AlarmManager.class, mAlarmManager::get);
         mProviders.put(KeyguardSecurityModel.class, mKeyguardSecurityModel::get);
+        mProviders.put(DozeParameters.class, mDozeParameters::get);
+        mProviders.put(IWallpaperManager.class, mWallpaperManager::get);
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
index 0d24321..9192eed 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
@@ -22,26 +22,36 @@
 import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.AlarmManager;
+import android.app.IActivityManager;
 import android.app.INotificationManager;
+import android.app.IWallpaperManager;
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.SensorPrivacyManager;
+import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.DisplayMetrics;
 import android.view.IWindowManager;
+import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.plugins.PluginInitializerImpl;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginManagerImpl;
@@ -49,6 +59,7 @@
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -58,7 +69,11 @@
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.util.leak.LeakDetector;
 
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
 import javax.inject.Named;
+import javax.inject.Qualifier;
 import javax.inject.Singleton;
 
 import dagger.Module;
@@ -70,6 +85,12 @@
  */
 @Module
 public class DependencyProvider {
+    @Qualifier
+    @Documented
+    @Retention(RUNTIME)
+    public @interface MainResources {
+        // TODO: use attribute to get other, non-main resources?
+    }
 
     @Singleton
     @Provides
@@ -204,8 +225,11 @@
     @Singleton
     @Provides
     public AutoHideController provideAutoHideController(Context context,
-            @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
-        return new AutoHideController(context, mainHandler);
+            @Named(MAIN_HANDLER_NAME) Handler mainHandler,
+            NotificationRemoteInputManager notificationRemoteInputManager,
+            IWindowManager iWindowManager) {
+        return new AutoHideController(context, mainHandler, notificationRemoteInputManager,
+                iWindowManager);
     }
 
     @Singleton
@@ -245,4 +269,48 @@
     public LockPatternUtils provideLockPatternUtils(Context context) {
         return new LockPatternUtils(context);
     }
+
+    /** */
+    @Provides
+    public AmbientDisplayConfiguration provideAmbientDispalyConfiguration(Context context) {
+        return new AmbientDisplayConfiguration(context);
+    }
+
+    /** */
+    @Provides
+    public AlwaysOnDisplayPolicy provideAlwaysOnDisplayPolicy(Context context) {
+        return new AlwaysOnDisplayPolicy(context);
+    }
+
+    /** */
+    @Provides
+    public PowerManager providePowerManager(Context context) {
+        return context.getSystemService(PowerManager.class);
+    }
+
+    /** */
+    @Provides
+    @MainResources
+    public Resources provideResources(Context context) {
+        return context.getResources();
+    }
+
+    /** */
+    @Provides
+    public IWallpaperManager provideWallPaperManager() {
+        return IWallpaperManager.Stub.asInterface(
+                ServiceManager.getService(Context.WALLPAPER_SERVICE));
+    }
+
+    /** */
+    @Provides
+    public WindowManager providesWindowManager(Context context) {
+        return context.getSystemService(WindowManager.class);
+    }
+
+    /** */
+    @Provides
+    public IActivityManager providesIActivityManager() {
+        return ActivityManager.getService();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 1c0e0b3..29a7167 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -38,6 +38,8 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+import javax.inject.Inject;
+
 /**
  * Default built-in wallpaper that simply shows a static image.
  */
@@ -50,8 +52,15 @@
     private static final int INTERVAL_WAIT_FOR_RENDERING = 100;
     private static final int PATIENCE_WAIT_FOR_RENDERING = 10;
     private static final boolean DEBUG = true;
+    private final DozeParameters mDozeParameters;
     private HandlerThread mWorker;
 
+    @Inject
+    public ImageWallpaper(DozeParameters dozeParameters) {
+        super();
+        mDozeParameters = dozeParameters;
+    }
+
     @Override
     public void onCreate() {
         super.onCreate();
@@ -61,7 +70,7 @@
 
     @Override
     public Engine onCreateEngine() {
-        return new GLEngine(this);
+        return new GLEngine(this, mDozeParameters);
     }
 
     @Override
@@ -89,9 +98,9 @@
         // This variable can only be accessed in synchronized block.
         private boolean mWaitingForRendering;
 
-        GLEngine(Context context) {
+        GLEngine(Context context, DozeParameters dozeParameters) {
             mNeedTransition = ActivityManager.isHighEndGfx()
-                    && !DozeParameters.getInstance(context).getDisplayNeedsBlanking();
+                    && !dozeParameters.getDisplayNeedsBlanking();
 
             // We will preserve EGL context when we are in lock screen or aod
             // to avoid janking in following transition, we need to release when back to home.
@@ -339,9 +348,9 @@
             boolean isHighEndGfx = ActivityManager.isHighEndGfx();
             out.print(prefix); out.print("isHighEndGfx="); out.println(isHighEndGfx);
 
-            DozeParameters dozeParameters = DozeParameters.getInstance(getApplicationContext());
             out.print(prefix); out.print("displayNeedsBlanking=");
-            out.println(dozeParameters != null ? dozeParameters.getDisplayNeedsBlanking() : "null");
+            out.println(
+                    mDozeParameters != null ? mDozeParameters.getDisplayNeedsBlanking() : "null");
 
             out.print(prefix); out.print("mNeedTransition="); out.println(mNeedTransition);
             out.print(prefix); out.print("StatusBarState=");
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 50f1b44..ddbabee 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -41,6 +41,10 @@
     private static final String ACTION_TURN_ON_SCREEN =
             "com.android.systemui.latency.ACTION_TURN_ON_SCREEN";
 
+    public LatencyTester(Context context) {
+        super(context);
+    }
+
     @Override
     public void start() {
         if (!Build.IS_DEBUGGABLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 3e068b0..ad20986 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -132,12 +132,15 @@
         return result;
     }
 
+    public ScreenDecorations(Context context) {
+        super(context);
+    }
+
     @Override
     public void start() {
         mHandler = startHandlerThread();
         mHandler.post(this::startOnScreenDecorationsThread);
         setupStatusBarPaddingIfNeeded();
-        putComponent(ScreenDecorations.class, this);
     }
 
     @VisibleForTesting
@@ -457,7 +460,7 @@
                         | WindowManager.LayoutParams.FLAG_SLIPPERY
                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
                 PixelFormat.TRANSLUCENT);
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
+        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
                 | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 
         if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
diff --git a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
index e761a2b..c11236e 100644
--- a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
@@ -19,6 +19,7 @@
 import android.app.Service;
 
 import com.android.systemui.doze.DozeService;
+import com.android.systemui.keyguard.KeyguardService;
 
 import dagger.Binds;
 import dagger.Module;
@@ -30,8 +31,21 @@
  */
 @Module
 public abstract class ServiceBinder {
+    /** */
     @Binds
     @IntoMap
     @ClassKey(DozeService.class)
     public abstract Service bindDozeService(DozeService service);
+
+    /** */
+    @Binds
+    @IntoMap
+    @ClassKey(ImageWallpaper.class)
+    public abstract Service bindImageWallpaper(ImageWallpaper service);
+
+    /** */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardService.class)
+    public abstract Service bindKeyguardService(KeyguardService service);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
index c54f6306..10009f5 100644
--- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
@@ -59,12 +59,13 @@
     /** Only show once automatically in the process life. */
     private boolean mHasShownHint;
 
-    public SizeCompatModeActivityController() {
-        this(ActivityManagerWrapper.getInstance());
+    public SizeCompatModeActivityController(Context context) {
+        this(context, ActivityManagerWrapper.getInstance());
     }
 
     @VisibleForTesting
-    SizeCompatModeActivityController(ActivityManagerWrapper am) {
+    SizeCompatModeActivityController(Context context, ActivityManagerWrapper am) {
+        super(context);
         am.registerTaskStackListener(new TaskStackChangeListener() {
             @Override
             public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
@@ -202,7 +203,7 @@
             mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                     | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
             mWinParams.format = PixelFormat.TRANSLUCENT;
-            mWinParams.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+            mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
             mWinParams.setTitle(SizeCompatModeActivityController.class.getSimpleName()
                     + context.getDisplayId());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
index b3fc69e..92fbd25 100644
--- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
@@ -39,6 +39,10 @@
 
     private final ArrayMap<Uri, BroadcastRelay> mRelays = new ArrayMap<>();
 
+    public SliceBroadcastRelayHandler(Context context) {
+        super(context);
+    }
+
     @Override
     public void start() {
         if (DEBUG) Log.d(TAG, "Start");
diff --git a/packages/SystemUI/src/com/android/systemui/SysUIToast.java b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
index 8bcf057..0f7f1be 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
@@ -31,7 +31,7 @@
     public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
         Toast toast = Toast.makeText(context, text, duration);
         toast.getWindowParams().privateFlags |=
-                WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+                WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         return toast;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUI.java b/packages/SystemUI/src/com/android/systemui/SystemUI.java
index 30fbef6..7570037 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUI.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUI.java
@@ -26,9 +26,13 @@
 import java.util.Map;
 
 public abstract class SystemUI implements SysUiServiceProvider {
-    public Context mContext;
+    protected final Context mContext;
     public Map<Class<?>, Object> mComponents;
 
+    public SystemUI(Context context) {
+        mContext = context;
+    }
+
     public abstract void start();
 
     protected void onConfigurationChanged(Configuration newConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 56b5d08..91776a3 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -42,6 +42,8 @@
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.util.NotificationChannels;
 
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -193,18 +195,18 @@
             try {
                 SystemUI obj = mComponentHelper.resolveSystemUI(clsName);
                 if (obj == null) {
-                    obj = (SystemUI) Class.forName(clsName).newInstance();
+                    Constructor constructor = Class.forName(clsName).getConstructor(Context.class);
+                    obj = (SystemUI) constructor.newInstance(this);
                 }
                 mServices[i] = obj;
-            } catch (ClassNotFoundException ex) {
-                throw new RuntimeException(ex);
-            } catch (IllegalAccessException ex) {
-                throw new RuntimeException(ex);
-            } catch (InstantiationException ex) {
+            } catch (ClassNotFoundException
+                    | NoSuchMethodException
+                    | IllegalAccessException
+                    | InstantiationException
+                    | InvocationTargetException ex) {
                 throw new RuntimeException(ex);
             }
 
-            mServices[i].mContext = this;
             mServices[i].mComponents = mComponents;
             if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
             mServices[i].start();
@@ -235,7 +237,7 @@
                                 if (statusBar != null) {
                                     plugin.setup(statusBar.getStatusBarWindow(),
                                             statusBar.getNavigationBarView(), new Callback(plugin),
-                                            DozeParameters.getInstance(getBaseContext()));
+                                            Dependency.get(DozeParameters.class));
                                 }
                             }
                         });
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
index 785038f..a5a5598 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
@@ -17,10 +17,12 @@
 package com.android.systemui;
 
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.pip.PipUI;
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsModule;
 import com.android.systemui.util.leak.GarbageMonitor;
+import com.android.systemui.volume.VolumeUI;
 
 import dagger.Binds;
 import dagger.Module;
@@ -32,12 +34,25 @@
  */
 @Module(includes = {RecentsModule.class})
 public abstract class SystemUIBinder {
+
+    /** Inject into GarbageMonitor.Service. */
+    @Binds
+    @IntoMap
+    @ClassKey(GarbageMonitor.Service.class)
+    public abstract SystemUI bindGarbageMonitorService(GarbageMonitor.Service service);
+
     /** Inject into KeyguardViewMediator. */
     @Binds
     @IntoMap
     @ClassKey(KeyguardViewMediator.class)
     public abstract SystemUI bindKeyguardViewMediator(KeyguardViewMediator sysui);
 
+    /** Inject into PipUI. */
+    @Binds
+    @IntoMap
+    @ClassKey(PipUI.class)
+    public abstract SystemUI bindPipUI(PipUI sysui);
+
     /** Inject into PowerUI. */
     @Binds
     @IntoMap
@@ -50,9 +65,10 @@
     @ClassKey(Recents.class)
     public abstract SystemUI bindRecents(Recents sysui);
 
-    /** Inject into GarbageMonitor.Service. */
+    /** Inject into VolumeUI. */
     @Binds
     @IntoMap
-    @ClassKey(GarbageMonitor.Service.class)
-    public abstract SystemUI bindGarbageMonitorService(GarbageMonitor.Service service);
+    @ClassKey(VolumeUI.class)
+    public abstract SystemUI bindVolumeUI(VolumeUI sysui);
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 530dcdc..ef7526b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -48,7 +48,6 @@
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.volume.VolumeDialogComponent;
 
 import java.util.function.Consumer;
 
@@ -161,7 +160,8 @@
             StatusBarStateController statusBarStateController) {
         return new NotificationIconAreaController(context, statusBar, statusBarStateController,
                 wakeUpCoordinator, keyguardBypassController,
-                Dependency.get(NotificationMediaManager.class));
+                Dependency.get(NotificationMediaManager.class),
+                Dependency.get(DozeParameters.class));
     }
 
     public KeyguardIndicationController createKeyguardIndicationController(Context context,
@@ -169,10 +169,6 @@
         return new KeyguardIndicationController(context, indicationArea, lockIcon);
     }
 
-    public VolumeDialogComponent createVolumeDialogComponent(SystemUI systemUi, Context context) {
-        return new VolumeDialogComponent(systemUi, context);
-    }
-
     @Module
     public static class ContextHolder {
         private Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/VendorServices.java b/packages/SystemUI/src/com/android/systemui/VendorServices.java
index 0be6b12..13d847b 100644
--- a/packages/SystemUI/src/com/android/systemui/VendorServices.java
+++ b/packages/SystemUI/src/com/android/systemui/VendorServices.java
@@ -16,11 +16,17 @@
 
 package com.android.systemui;
 
+import android.content.Context;
+
 /**
  * Placeholder for any vendor-specific services.
  */
 public class VendorServices extends SystemUI {
 
+    public VendorServices(Context context) {
+        super(context);
+    }
+
     @Override
     public void start() {
         // no-op
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
index 739eade..6f5a17d 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
@@ -21,6 +21,7 @@
 import android.os.HandlerThread;
 import android.os.SystemClock;
 
+import androidx.annotation.Nullable;
 import androidx.slice.Clock;
 
 import com.android.internal.app.AssistUtils;
@@ -68,6 +69,7 @@
     }
 
     @Provides
+    @Nullable
     static AssistHandleViewController provideAssistHandleViewController(
             NavigationBarController navigationBarController) {
         return navigationBarController.getAssistHandlerViewController();
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 9958124..4cb1708 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -164,8 +164,11 @@
     }
 
     private void updateAssistHandleVisibility() {
-        AssistHandleViewController controller = Dependency.get(NavigationBarController.class)
-                .getAssistHandlerViewController();
+        NavigationBarController navigationBarController =
+                Dependency.get(NavigationBarController.class);
+        AssistHandleViewController controller =
+                navigationBarController == null
+                        ? null : navigationBarController.getAssistHandlerViewController();
         if (controller != null) {
             controller.setAssistHintBlocked(mInvocationInProgress);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index a9359d4..f1abdb3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -580,7 +580,7 @@
                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
                 WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                 PixelFormat.TRANSLUCENT);
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("BiometricPrompt");
         lp.token = windowToken;
         return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 4c2afb0..cdc2623 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -172,12 +172,13 @@
         }
     }
 
-    public AuthController() {
-        this(new Injector());
+    public AuthController(Context context) {
+        this(context, new Injector());
     }
 
     @VisibleForTesting
-    AuthController(Injector injector) {
+    AuthController(Context context, Injector injector) {
+        super(context);
         mInjector = injector;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index 8df072e..bebaa4b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -26,7 +26,7 @@
 import android.widget.TextView;
 
 import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.systemui.R;
 
 /**
@@ -96,13 +96,16 @@
     }
 
     private void checkPasswordAndUnlock() {
-        final byte[] password = LockPatternUtils.charSequenceToByteArray(mPasswordField.getText());
-        if (password == null || password.length == 0) {
-            return;
-        }
+        try (LockscreenCredential password =  mCredentialType == Utils.CREDENTIAL_PIN
+                ? LockscreenCredential.createPinOrNone(mPasswordField.getText())
+                : LockscreenCredential.createPasswordOrNone(mPasswordField.getText())) {
+            if (password.isNone()) {
+                return;
+            }
 
-        mPendingLockCheck = LockPatternChecker.checkPassword(mLockPatternUtils,
-                password, mUserId, this::onCredentialChecked);
+            mPendingLockCheck = LockPatternChecker.checkCredential(mLockPatternUtils,
+                    password, mUserId, this::onCredentialChecked);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
index 6c36f82..14414a4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
@@ -22,6 +22,7 @@
 import com.android.internal.widget.LockPatternChecker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.systemui.R;
 
 import java.util.List;
@@ -64,11 +65,13 @@
                 return;
             }
 
-            mPendingLockCheck = LockPatternChecker.checkPattern(
-                    mLockPatternUtils,
-                    pattern,
-                    mUserId,
-                    this::onPatternChecked);
+            try (LockscreenCredential credential = LockscreenCredential.createPattern(pattern)) {
+                mPendingLockCheck = LockPatternChecker.checkCredential(
+                        mLockPatternUtils,
+                        credential,
+                        mUserId,
+                        this::onPatternChecked);
+            }
         }
 
         private void onPatternChecked(boolean matched, int timeoutMs) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java
index 85a4d23..b726c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.classifier.brightline;
 
+import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
+import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+
 import android.view.MotionEvent;
 
 import java.util.Locale;
@@ -29,6 +32,7 @@
 class PointerCountClassifier extends FalsingClassifier {
 
     private static final int MAX_ALLOWED_POINTERS = 1;
+    private static final int MAX_ALLOWED_POINTERS_SWIPE_DOWN = 2;
     private int mMaxPointerCount;
 
     PointerCountClassifier(FalsingDataProvider dataProvider) {
@@ -50,6 +54,10 @@
 
     @Override
     public boolean isFalseTouch() {
+        int interactionType = getInteractionType();
+        if (interactionType == QUICK_SETTINGS || interactionType == NOTIFICATION_DRAG_DOWN) {
+            return mMaxPointerCount > MAX_ALLOWED_POINTERS_SWIPE_DOWN;
+        }
         return mMaxPointerCount > MAX_ALLOWED_POINTERS;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index ca4ec6d..3f0505f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -18,6 +18,7 @@
 
 import android.app.AlarmManager;
 import android.app.Application;
+import android.app.IWallpaperManager;
 import android.content.Context;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
@@ -25,7 +26,6 @@
 import android.os.Handler;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.dock.DockManager;
@@ -39,47 +39,71 @@
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
+import javax.inject.Inject;
+
 public class DozeFactory {
 
-    public DozeFactory() {
+    private final FalsingManager mFalsingManager;
+    private final DozeLog mDozeLog;
+    private final DozeParameters mDozeParameters;
+    private final BatteryController mBatteryController;
+    private final AsyncSensorManager mAsyncSensorManager;
+    private final AlarmManager mAlarmManager;
+    private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final DockManager mDockManager;
+    private final IWallpaperManager mWallpaperManager;
+
+    @Inject
+    public DozeFactory(FalsingManager falsingManager, DozeLog dozeLog,
+            DozeParameters dozeParameters, BatteryController batteryController,
+            AsyncSensorManager asyncSensorManager, AlarmManager alarmManager,
+            WakefulnessLifecycle wakefulnessLifecycle, KeyguardUpdateMonitor keyguardUpdateMonitor,
+            DockManager dockManager, IWallpaperManager wallpaperManager) {
+        mFalsingManager = falsingManager;
+        mDozeLog = dozeLog;
+        mDozeParameters = dozeParameters;
+        mBatteryController = batteryController;
+        mAsyncSensorManager = asyncSensorManager;
+        mAlarmManager = alarmManager;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mDockManager = dockManager;
+        mWallpaperManager = wallpaperManager;
     }
 
     /** Creates a DozeMachine with its parts for {@code dozeService}. */
-    public DozeMachine assembleMachine(DozeService dozeService, FalsingManager falsingManager,
-            DozeLog dozeLog) {
-        Context context = dozeService;
-        AsyncSensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
-        AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
-        DockManager dockManager = Dependency.get(DockManager.class);
-        WakefulnessLifecycle wakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
-
+    public DozeMachine assembleMachine(DozeService dozeService) {
         DozeHost host = getHost(dozeService);
-        AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(context);
-        DozeParameters params = DozeParameters.getInstance(context);
+        AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(dozeService);
         Handler handler = new Handler();
         WakeLock wakeLock = new DelayedWakeLock(handler,
-                WakeLock.createPartial(context, "Doze"));
+                WakeLock.createPartial(dozeService, "Doze"));
 
         DozeMachine.Service wrappedService = dozeService;
         wrappedService = new DozeBrightnessHostForwarder(wrappedService, host);
-        wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded(wrappedService, params);
-        wrappedService = DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(wrappedService,
-                params);
+        wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded(
+                wrappedService, mDozeParameters);
+        wrappedService = DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(
+                wrappedService, mDozeParameters);
 
         DozeMachine machine = new DozeMachine(wrappedService, config, wakeLock,
-                wakefulnessLifecycle, Dependency.get(BatteryController.class), dozeLog);
+                                              mWakefulnessLifecycle, mBatteryController, mDozeLog);
         machine.setParts(new DozeMachine.Part[]{
-                new DozePauser(handler, machine, alarmManager, params.getPolicy()),
-                new DozeFalsingManagerAdapter(falsingManager),
-                createDozeTriggers(context, sensorManager, host, alarmManager, config, params,
-                        handler, wakeLock, machine, dockManager, dozeLog),
-                createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params,
-                        dozeLog),
-                new DozeScreenState(wrappedService, handler, params, wakeLock),
-                createDozeScreenBrightness(context, wrappedService, sensorManager, host, params,
-                        handler),
-                new DozeWallpaperState(context, getBiometricUnlockController(dozeService)),
-                new DozeDockHandler(context, machine, host, config, handler, dockManager),
+                new DozePauser(handler, machine, mAlarmManager, mDozeParameters.getPolicy()),
+                new DozeFalsingManagerAdapter(mFalsingManager),
+                createDozeTriggers(dozeService, mAsyncSensorManager, host, mAlarmManager, config,
+                        mDozeParameters, handler, wakeLock, machine, mDockManager, mDozeLog),
+                createDozeUi(dozeService, host, wakeLock, machine, handler, mAlarmManager,
+                        mDozeParameters, mDozeLog),
+                new DozeScreenState(wrappedService, handler, host, mDozeParameters, wakeLock),
+                createDozeScreenBrightness(dozeService, wrappedService, mAsyncSensorManager, host,
+                        mDozeParameters, handler),
+                new DozeWallpaperState(
+                        mWallpaperManager,
+                        getBiometricUnlockController(dozeService),
+                        mDozeParameters),
+                new DozeDockHandler(dozeService, machine, host, config, handler, mDockManager),
                 new DozeAuthRemover(dozeService)
         });
 
@@ -110,7 +134,7 @@
             DozeMachine machine, Handler handler, AlarmManager alarmManager,
             DozeParameters params, DozeLog dozeLog) {
         return new DozeUi(context, alarmManager, machine, wakeLock, host, handler, params,
-                Dependency.get(KeyguardUpdateMonitor.class), dozeLog);
+                          mKeyguardUpdateMonitor, dozeLog);
     }
 
     public static DozeHost getHost(DozeService service) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 07dd2cd..1a6bd60 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -63,9 +63,10 @@
     void setDozeScreenBrightness(int value);
 
     /**
-     * Makes scrims black and changes animation durations.
+     * Fade out screen before switching off the display power mode.
+     * @param onDisplayOffCallback Executed when the display is black.
      */
-    default void prepareForGentleWakeUp() {}
+    void prepareForGentleSleep(Runnable onDisplayOffCallback);
 
     void onIgnoreTouchWhilePulsing(boolean ignore);
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 38ee2fe..95c42fc 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -16,6 +16,12 @@
 
 package com.android.systemui.doze;
 
+import static com.android.systemui.doze.DozeMachine.State.DOZE;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
+
 import android.os.Handler;
 import android.util.Log;
 import android.view.Display;
@@ -48,21 +54,24 @@
     private final Handler mHandler;
     private final Runnable mApplyPendingScreenState = this::applyPendingScreenState;
     private final DozeParameters mParameters;
+    private final DozeHost mDozeHost;
 
     private int mPendingScreenState = Display.STATE_UNKNOWN;
     private SettableWakeLock mWakeLock;
 
-    public DozeScreenState(DozeMachine.Service service, Handler handler,
+    public DozeScreenState(DozeMachine.Service service, Handler handler, DozeHost host,
             DozeParameters parameters, WakeLock wakeLock) {
         mDozeService = service;
         mHandler = handler;
         mParameters = parameters;
+        mDozeHost = host;
         mWakeLock = new SettableWakeLock(wakeLock, TAG);
     }
 
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         int screenState = newState.screenState(mParameters);
+        mDozeHost.prepareForGentleSleep(null);
 
         if (newState == DozeMachine.State.FINISH) {
             // Make sure not to apply the screen state after DozeService was destroyed.
@@ -79,12 +88,13 @@
             return;
         }
 
-        boolean messagePending = mHandler.hasCallbacks(mApplyPendingScreenState);
-        boolean pulseEnding = oldState  == DozeMachine.State.DOZE_PULSE_DONE
-                && newState == DozeMachine.State.DOZE_AOD;
-        boolean turningOn = (oldState == DozeMachine.State.DOZE_AOD_PAUSED
-                || oldState  == DozeMachine.State.DOZE) && newState == DozeMachine.State.DOZE_AOD;
-        boolean justInitialized = oldState == DozeMachine.State.INITIALIZED;
+        final boolean messagePending = mHandler.hasCallbacks(mApplyPendingScreenState);
+        final boolean pulseEnding = oldState  == DOZE_PULSE_DONE && newState == DOZE_AOD;
+        final boolean turningOn = (oldState == DOZE_AOD_PAUSED
+                || oldState  == DOZE) && newState == DOZE_AOD;
+        final boolean turningOff = (oldState == DOZE_AOD && newState == DOZE)
+                || (oldState == DOZE_AOD_PAUSING && newState == DOZE_AOD_PAUSED);
+        final boolean justInitialized = oldState == DozeMachine.State.INITIALIZED;
         if (messagePending || justInitialized || pulseEnding || turningOn) {
             // During initialization, we hide the navigation bar. That is however only applied after
             // a traversal; setting the screen state here is immediate however, so it can happen
@@ -93,7 +103,7 @@
             mPendingScreenState = screenState;
 
             // Delay screen state transitions even longer while animations are running.
-            boolean shouldDelayTransition = newState == DozeMachine.State.DOZE_AOD
+            boolean shouldDelayTransition = newState == DOZE_AOD
                     && mParameters.shouldControlScreenOff() && !turningOn;
 
             if (shouldDelayTransition) {
@@ -114,6 +124,8 @@
             } else if (DEBUG) {
                 Log.d(TAG, "Pending display state change to " + screenState);
             }
+        } else if (turningOff) {
+            mDozeHost.prepareForGentleSleep(() -> applyScreenState(screenState));
         } else {
             applyScreenState(screenState);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 17559c9..08734d2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -22,10 +22,8 @@
 import android.service.dreams.DreamService;
 import android.util.Log;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.plugins.DozeServicePlugin;
 import com.android.systemui.plugins.DozeServicePlugin.RequestDoze;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.plugins.PluginManager;
 
@@ -38,18 +36,17 @@
         implements DozeMachine.Service, RequestDoze, PluginListener<DozeServicePlugin> {
     private static final String TAG = "DozeService";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private final FalsingManager mFalsingManager;
-    private final DozeLog mDozeLog;
+    private final DozeFactory mDozeFactory;
 
     private DozeMachine mDozeMachine;
     private DozeServicePlugin mDozePlugin;
     private PluginManager mPluginManager;
 
     @Inject
-    public DozeService(FalsingManager falsingManager, DozeLog dozeLog) {
+    public DozeService(DozeFactory dozeFactory, PluginManager pluginManager) {
         setDebug(DEBUG);
-        mFalsingManager = falsingManager;
-        mDozeLog = dozeLog;
+        mDozeFactory = dozeFactory;
+        mPluginManager = pluginManager;
     }
 
     @Override
@@ -62,9 +59,8 @@
             finish();
             return;
         }
-        mPluginManager = Dependency.get(PluginManager.class);
         mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */);
-        mDozeMachine = new DozeFactory().assembleMachine(this, mFalsingManager, mDozeLog);
+        mDozeMachine = mDozeFactory.assembleMachine(this);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 2c0ccd21..f155783 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -135,7 +135,6 @@
                 break;
             case DOZE:
             case DOZE_AOD_PAUSED:
-                mHost.prepareForGentleWakeUp();
                 unscheduleTimeTick();
                 break;
             case DOZE_REQUEST_PULSE:
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index 35c8b74..9457dc9 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -17,12 +17,9 @@
 package com.android.systemui.doze;
 
 import android.app.IWallpaperManager;
-import android.content.Context;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -42,17 +39,10 @@
     private final BiometricUnlockController mBiometricUnlockController;
     private boolean mIsAmbientMode;
 
-    public DozeWallpaperState(Context context,
-            BiometricUnlockController biometricUnlockController) {
-        this(IWallpaperManager.Stub.asInterface(
-                ServiceManager.getService(Context.WALLPAPER_SERVICE)),
-                biometricUnlockController,
-                DozeParameters.getInstance(context));
-    }
-
-    @VisibleForTesting
-    DozeWallpaperState(IWallpaperManager wallpaperManagerService,
-            BiometricUnlockController biometricUnlockController, DozeParameters parameters) {
+    public DozeWallpaperState(
+            IWallpaperManager wallpaperManagerService,
+            BiometricUnlockController biometricUnlockController,
+            DozeParameters parameters) {
         mWallpaperManagerService = wallpaperManagerService;
         mBiometricUnlockController = biometricUnlockController;
         mDozeParameters = parameters;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index e8ef454..c11127d 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -35,6 +35,10 @@
     private Extension<GlobalActions> mExtension;
     private IStatusBarService mBarService;
 
+    public GlobalActionsComponent(Context context) {
+        super(context);
+    }
+
     @Override
     public void start() {
         mBarService = IStatusBarService.Stub.asInterface(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 7d52a9a..42f455a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -117,6 +117,10 @@
 
     private int mState;
 
+    public KeyboardUI(Context context) {
+        super(context);
+    }
+
     @Override
     public void start() {
         mContext = super.mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
index b3481c5..4a33590 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
@@ -19,9 +19,13 @@
 import android.os.Handler;
 import android.os.Message;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 /**
  * Dispatches the lifecycles keyguard gets from WindowManager on the main thread.
  */
+@Singleton
 public class KeyguardLifecyclesDispatcher {
 
     static final int SCREEN_TURNING_ON = 0;
@@ -37,6 +41,7 @@
     private final ScreenLifecycle mScreenLifecycle;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
 
+    @Inject
     public KeyguardLifecyclesDispatcher(ScreenLifecycle screenLifecycle,
             WakefulnessLifecycle wakefulnessLifecycle) {
         mScreenLifecycle = screenLifecycle;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 81247cd..9f4056f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -33,25 +33,30 @@
 import com.android.internal.policy.IKeyguardExitCallback;
 import com.android.internal.policy.IKeyguardService;
 import com.android.internal.policy.IKeyguardStateCallback;
-import com.android.systemui.Dependency;
 import com.android.systemui.SystemUIApplication;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
 public class KeyguardService extends Service {
     static final String TAG = "KeyguardService";
     static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;
 
-    private KeyguardViewMediator mKeyguardViewMediator;
-    private KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
+    private final KeyguardViewMediator mKeyguardViewMediator;
+    private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
+
+    @Inject
+    public KeyguardService(KeyguardViewMediator keyguardViewMediator,
+                           KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher) {
+        super();
+        mKeyguardViewMediator = keyguardViewMediator;
+        mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
+    }
 
     @Override
     public void onCreate() {
         ((SystemUIApplication) getApplication()).startServicesIfNeeded();
-        mKeyguardViewMediator =
-                ((SystemUIApplication) getApplication()).getComponent(KeyguardViewMediator.class);
-        mKeyguardLifecyclesDispatcher = new KeyguardLifecyclesDispatcher(
-                Dependency.get(ScreenLifecycle.class),
-                Dependency.get(WakefulnessLifecycle.class));
-
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a8027c0..e0270de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -685,9 +685,8 @@
             Context context,
             FalsingManager falsingManager,
             LockPatternUtils lockPatternUtils) {
-        super();
+        super(context);
 
-        mContext = context;
         mFalsingManager = falsingManager;
 
         mLockPatternUtils = lockPatternUtils;
@@ -795,7 +794,6 @@
         synchronized (this) {
             setupLocked();
         }
-        putComponent(KeyguardViewMediator.class, this);
     }
 
     /**
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/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index aebadf9..4c96de2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -59,6 +59,10 @@
     private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG);
     private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
 
+    public RingtonePlayer(Context context) {
+        super(context);
+    }
+
     @Override
     public void start() {
         mAsyncPlayer.setUsesWakeLock(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
index 682c76c..f1e801b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
@@ -19,6 +19,7 @@
 import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.os.UserHandle;
@@ -30,15 +31,24 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 /**
  * Controls the picture-in-picture window.
  */
+@Singleton
 public class PipUI extends SystemUI implements CommandQueue.Callbacks {
 
     private BasePipManager mPipManager;
 
     private boolean mSupportsPip;
 
+    @Inject
+    public PipUI(Context context) {
+        super(context);
+    }
+
     @Override
     public void start() {
         PackageManager pm = mContext.getPackageManager();
@@ -59,7 +69,6 @@
         mPipManager.initialize(mContext);
 
         getComponent(CommandQueue.class).addCallback(this);
-        putComponent(PipUI.class, this);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
index 8224365..3f15966 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
@@ -107,7 +107,7 @@
                             | LayoutParams.FLAG_NOT_FOCUSABLE,
                     PixelFormat.TRANSLUCENT);
             lp.setTitle("pip-dismiss-overlay");
-            lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+            lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
             lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
             mWindowManager.addView(mDismissView, lp);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index a258f35..98f0b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -103,7 +103,8 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
 
     @Inject
-    public PowerUI(BroadcastDispatcher broadcastDispatcher) {
+    public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher) {
+        super(context);
         mBroadcastDispatcher = broadcastDispatcher;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index ae83567..2e24403 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -28,10 +28,12 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.provider.Settings;
 import android.service.quicksettings.Tile;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
 import com.android.internal.logging.MetricsLogger;
@@ -49,6 +51,8 @@
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.settings.BrightnessController;
 import com.android.systemui.settings.ToggleSliderView;
+import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.phone.NPVPluginManager;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
 import com.android.systemui.tuner.TunerService;
@@ -98,6 +102,10 @@
     private BrightnessMirrorController mBrightnessMirrorController;
     private View mDivider;
 
+    private FrameLayout mPluginFrame;
+    private final PluginManager mPluginManager;
+    private NPVPluginManager mNPVPluginManager;
+
     public QSPanel(Context context) {
         this(context, null);
     }
@@ -106,9 +114,13 @@
         this(context, attrs, null);
     }
 
+    public QSPanel(Context context, AttributeSet attrs, DumpController dumpController) {
+        this(context, attrs, dumpController, null);
+    }
+
     @Inject
     public QSPanel(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
-            DumpController dumpController) {
+            DumpController dumpController, PluginManager pluginManager) {
         super(context, attrs);
         mContext = context;
 
@@ -136,6 +148,15 @@
         mBrightnessController = new BrightnessController(getContext(),
                 findViewById(R.id.brightness_slider));
         mDumpController = dumpController;
+        mPluginManager = pluginManager;
+        if (mPluginManager != null && Settings.System.getInt(
+                mContext.getContentResolver(), "npv_plugin_flag", 0) == 2) {
+            mPluginFrame = (FrameLayout) LayoutInflater.from(mContext).inflate(
+                    R.layout.status_bar_expanded_plugin_frame, this, false);
+            addView(mPluginFrame);
+            mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+        }
+
     }
 
     protected void addDivider() {
@@ -377,6 +398,7 @@
         if (mListening) {
             refreshAllTiles();
         }
+        if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening);
     }
 
     public void setListening(boolean listening, boolean expanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 14addb9..37743ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -2,6 +2,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.provider.Settings;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
@@ -31,6 +32,9 @@
     private boolean mListening;
     protected int mMaxAllowedRows = 3;
 
+    // Prototyping with less rows
+    private final boolean mLessRows;
+
     public TileLayout(Context context) {
         this(context, null);
     }
@@ -38,7 +42,9 @@
     public TileLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
         setFocusableInTouchMode(true);
+        mLessRows = Settings.System.getInt(context.getContentResolver(), "qs_less_rows", 0) != 0;
         updateResources();
+
     }
 
     @Override
@@ -89,6 +95,7 @@
         mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top);
         mSidePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_layout_margin_side);
         mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows));
+        if (mLessRows) mMaxAllowedRows = Math.max(1, mMaxAllowedRows - 1);
         if (mColumns != columns) {
             mColumns = columns;
             requestLayout();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 466c808..411980b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -39,6 +39,7 @@
 import android.util.Log;
 import android.view.IWindowManager;
 import android.view.WindowManagerGlobal;
+import android.widget.Switch;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
@@ -82,6 +83,11 @@
         mTile = new Tile();
         updateDefaultTileAndIcon();
         mServiceManager = host.getTileServices().getTileWrapper(this);
+        if (mServiceManager.isBooleanTile()) {
+            // Replace states with BooleanState
+            resetStates();
+        }
+
         mService = mServiceManager.getTileService();
         mServiceManager.setTileChangeListener(this);
         mUser = ActivityManager.getCurrentUser();
@@ -246,8 +252,10 @@
 
     @Override
     public State newTileState() {
-        State state = new State();
-        return state;
+        if (mServiceManager != null && mServiceManager.isBooleanTile()) {
+            return new BooleanState();
+        }
+        return new State();
     }
 
     @Override
@@ -336,6 +344,12 @@
         } else {
             state.contentDescription = state.label;
         }
+
+        if (state instanceof BooleanState) {
+            state.expandedAccessibilityClassName = Switch.class.getName();
+            ((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE);
+        }
+
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index effea6a..f59e0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -131,6 +131,24 @@
     }
 
     /**
+     * Determines whether the associated TileService is a Boolean Tile.
+     *
+     * @return true if {@link TileService#META_DATA_BOOLEAN_TILE} is set to {@code true} for this
+     *         tile
+     * @see TileService#META_DATA_BOOLEAN_TILE
+     */
+    public boolean isBooleanTile() {
+        try {
+            ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
+            return info.metaData != null
+                    && info.metaData.getBoolean(TileService.META_DATA_BOOLEAN_TILE, false);
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
      * Binds just long enough to send any queued messages, then unbinds.
      */
     public void flushMessagesAndUnbind() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index 2a7e55f..0b4e648 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -123,6 +123,10 @@
         return mStateManager.isActiveTile();
     }
 
+    public boolean isBooleanTile() {
+        return mStateManager.isBooleanTile();
+    }
+
     public void setShowingDialog(boolean dialog) {
         mShowingDialog = dialog;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index daaee4c..1c8e451 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -16,6 +16,7 @@
 
 import android.content.Context;
 import android.os.Build;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 
@@ -32,6 +33,7 @@
 import com.android.systemui.qs.tiles.CastTile;
 import com.android.systemui.qs.tiles.CellularTile;
 import com.android.systemui.qs.tiles.ColorInversionTile;
+import com.android.systemui.qs.tiles.ControlsTile;
 import com.android.systemui.qs.tiles.DataSaverTile;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.FlashlightTile;
@@ -58,6 +60,7 @@
 
     private final Provider<WifiTile> mWifiTileProvider;
     private final Provider<BluetoothTile> mBluetoothTileProvider;
+    private final Provider<ControlsTile> mControlsTileProvider;
     private final Provider<CellularTile> mCellularTileProvider;
     private final Provider<DndTile> mDndTileProvider;
     private final Provider<ColorInversionTile> mColorInversionTileProvider;
@@ -81,6 +84,7 @@
     @Inject
     public QSFactoryImpl(Provider<WifiTile> wifiTileProvider,
             Provider<BluetoothTile> bluetoothTileProvider,
+            Provider<ControlsTile> controlsTileProvider,
             Provider<CellularTile> cellularTileProvider,
             Provider<DndTile> dndTileProvider,
             Provider<ColorInversionTile> colorInversionTileProvider,
@@ -100,6 +104,7 @@
             Provider<UiModeNightTile> uiModeNightTileProvider) {
         mWifiTileProvider = wifiTileProvider;
         mBluetoothTileProvider = bluetoothTileProvider;
+        mControlsTileProvider = controlsTileProvider;
         mCellularTileProvider = cellularTileProvider;
         mDndTileProvider = dndTileProvider;
         mColorInversionTileProvider = colorInversionTileProvider;
@@ -138,6 +143,11 @@
                 return mWifiTileProvider.get();
             case "bt":
                 return mBluetoothTileProvider.get();
+            case "controls":
+                if (Settings.System.getInt(mHost.getContext().getContentResolver(),
+                        "qs_controls_tile_enabled", 0) == 1) {
+                    return mControlsTileProvider.get();
+                } else return null;
             case "cell":
                 return mCellularTileProvider.get();
             case "dnd":
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 681de37..e0f26cd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -139,6 +139,11 @@
         mQSSettingsPanelOption = QSSettingsControllerKt.getQSSettingsPanelOption();
     }
 
+    protected final void resetStates() {
+        mState = newTileState();
+        mTmpState = newTileState();
+    }
+
     @NonNull
     @Override
     public Lifecycle getLifecycle() {
@@ -629,6 +634,11 @@
         }
     }
 
+    /**
+     * Dumps the state of this tile along with its name.
+     *
+     * This may be used for CTS testing of tiles.
+     */
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println(this.getClass().getSimpleName() + ":");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java
new file mode 100644
index 0000000..0a59618
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.HomeControlsPlugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.qs.DetailAdapter;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.shared.plugins.PluginManager;
+
+import javax.inject.Inject;
+
+
+/**
+ * Temporary control test for prototyping
+ */
+public class ControlsTile extends QSTileImpl<BooleanState> {
+    private ControlsDetailAdapter mDetailAdapter;
+    private final ActivityStarter mActivityStarter;
+    private PluginManager mPluginManager;
+    private HomeControlsPlugin mPlugin;
+    private Intent mHomeAppIntent;
+
+    @Inject
+    public ControlsTile(QSHost host,
+            ActivityStarter activityStarter,
+            PluginManager pluginManager) {
+        super(host);
+        mActivityStarter = activityStarter;
+        mPluginManager = pluginManager;
+        mDetailAdapter = (ControlsDetailAdapter) createDetailAdapter();
+
+        mHomeAppIntent = new Intent(Intent.ACTION_VIEW);
+        mHomeAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mHomeAppIntent.setComponent(new ComponentName("com.google.android.apps.chromecast.app",
+                "com.google.android.apps.chromecast.app.DiscoveryActivity"));
+    }
+
+    @Override
+    public DetailAdapter getDetailAdapter() {
+        return mDetailAdapter;
+    }
+
+    @Override
+    public BooleanState newTileState() {
+        return new BooleanState();
+    }
+
+    @Override
+    public void handleSetListening(boolean listening) {
+
+    }
+
+    @Override
+    public void setDetailListening(boolean listening) {
+        if (mPlugin == null) return;
+
+        mPlugin.setVisible(listening);
+    }
+
+    @Override
+    protected void handleClick() {
+        showDetail(true);
+    }
+
+    @Override
+    public Intent getLongClickIntent() {
+        return mHomeAppIntent;
+    }
+
+    @Override
+    protected void handleSecondaryClick() {
+        showDetail(true);
+    }
+
+    @Override
+    public CharSequence getTileLabel() {
+        return "Controls";
+    }
+
+    @Override
+    protected void handleUpdateState(BooleanState state, Object arg) {
+        state.icon = ResourceIcon.get(R.drawable.ic_lightbulb_outline_gm2_24px);
+        state.label = "Controls";
+    }
+
+    @Override
+    public boolean supportsDetailView() {
+        return getDetailAdapter() != null && mQSSettingsPanelOption == QSSettingsPanel.OPEN_CLICK;
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return -1;
+    }
+
+    @Override
+    protected String composeChangeAnnouncement() {
+        if (mState.value) {
+            return "On";
+        } else {
+            return "Off";
+        }
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    protected DetailAdapter createDetailAdapter() {
+        mDetailAdapter = new ControlsDetailAdapter();
+        return mDetailAdapter;
+    }
+
+    private class ControlsDetailAdapter implements DetailAdapter {
+        private View mDetailView;
+        protected LinearLayout mHomeControlsLayout;
+
+        public CharSequence getTitle() {
+            return "Controls";
+        }
+
+        public Boolean getToggleState() {
+            return null;
+        }
+
+        public boolean getToggleEnabled() {
+            return false;
+        }
+
+        public View createDetailView(Context context, View convertView, final ViewGroup parent) {
+            mHomeControlsLayout = (LinearLayout) LayoutInflater.from(context).inflate(
+                R.layout.home_controls, parent, false);
+            mHomeControlsLayout.setVisibility(View.VISIBLE);
+            mPluginManager.addPluginListener(
+                    new PluginListener<HomeControlsPlugin>() {
+                        @Override
+                        public void onPluginConnected(HomeControlsPlugin plugin,
+                                                      Context pluginContext) {
+                            mPlugin = plugin;
+                            mPlugin.sendParentGroup(mHomeControlsLayout);
+                            mPlugin.setVisible(true);
+                        }
+
+                        @Override
+                        public void onPluginDisconnected(HomeControlsPlugin plugin) {
+
+                        }
+                    }, HomeControlsPlugin.class, false);
+            return mHomeControlsLayout;
+        }
+
+        public Intent getSettingsIntent() {
+            return mHomeAppIntent;
+        }
+
+        public void setToggleState(boolean state) {
+
+        }
+
+        public int getMetricsCategory() {
+            return -1;
+        }
+
+        public boolean hasHeader() {
+            return false;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index e0ae8ed..3fc1398 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -101,6 +101,7 @@
     private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
 
     private final Context mContext;
+    private final PipUI mPipUI;
     private SysUiState mSysUiState;
     private final Handler mHandler;
     private final NavigationBarController mNavBarController;
@@ -361,8 +362,7 @@
             }
             long token = Binder.clearCallingIdentity();
             try {
-                final PipUI component = SysUiServiceProvider.getComponent(mContext, PipUI.class);
-                component.setShelfHeight(visible, shelfHeight);
+                mPipUI.setShelfHeight(visible, shelfHeight);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -479,8 +479,9 @@
     public OverviewProxyService(Context context, DeviceProvisionedController provisionController,
             NavigationBarController navBarController, NavigationModeController navModeController,
             StatusBarWindowController statusBarWinController,
-            SysUiState sysUiState) {
+            SysUiState sysUiState, PipUI pipUI) {
         mContext = context;
+        mPipUI = pipUI;
         mHandler = new Handler();
         mNavBarController = navBarController;
         mStatusBarWinController = statusBarWinController;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index a1b4a93..0a8264b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -17,6 +17,7 @@
 package com.android.systemui.recents;
 
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.provider.Settings;
@@ -37,7 +38,8 @@
     private final RecentsImplementation mImpl;
 
     @Inject
-    public Recents(RecentsImplementation impl) {
+    public Recents(Context context, RecentsImplementation impl) {
+        super(context);
         mImpl = impl;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index c1ce163..aa64449 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -497,7 +497,7 @@
                 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                 flags,
                 PixelFormat.TRANSLUCENT);
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("RecentsOnboarding");
         lp.gravity = gravity;
         return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 0f277ca..2d1c087 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -128,7 +128,7 @@
                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                 PixelFormat.TRANSLUCENT);
         lp.token = new Binder();
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("ScreenPinningConfirmation");
         lp.gravity = Gravity.FILL;
         return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 07675e2..df9791d 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
 import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 
+import android.content.Context;
 import android.content.res.Configuration;
 import android.os.RemoteException;
 import android.util.Log;
@@ -52,6 +53,10 @@
     protected final long SC_DOCK_LEFT = META_MASK | KeyEvent.KEYCODE_LEFT_BRACKET;
     protected final long SC_DOCK_RIGHT = META_MASK | KeyEvent.KEYCODE_RIGHT_BRACKET;
 
+    public ShortcutKeyDispatcher(Context context) {
+        super(context);
+    }
+
     /**
      * Registers a shortcut key to window manager.
      * @param shortcutCode packed representation of shortcut key code and meta information
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index cd2074f..c8b2b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -19,6 +19,7 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
+import android.content.Context;
 import android.content.res.Configuration;
 import android.os.RemoteException;
 import android.util.Log;
@@ -50,6 +51,10 @@
     private boolean mHomeStackResizable = false;
     private ForcedResizableInfoActivityController mForcedResizableController;
 
+    public Divider(Context context) {
+        super(context);
+    }
+
     @Override
     public void start() {
         mWindowManager = new DividerWindowManager(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 36e04fe..d6a8f90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -1101,6 +1101,10 @@
     // Need this class since CommandQueue already extends IStatusBar.Stub, so CommandQueueStart
     // is needed so it can extend SystemUI.
     public static class CommandQueueStart extends SystemUI {
+        public CommandQueueStart(Context context) {
+            super(context);
+        }
+
         @Override
         public void start() {
             putComponent(CommandQueue.class, new CommandQueue(mContext));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 16cdfaa..5144a95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -152,7 +152,9 @@
             //                    Dependency problem.
             AutoHideController autoHideController = isOnDefaultDisplay
                     ? Dependency.get(AutoHideController.class)
-                    : new AutoHideController(context, mHandler);
+                    : new AutoHideController(context, mHandler,
+                            Dependency.get(NotificationRemoteInputManager.class),
+                            Dependency.get(IWindowManager.class));
             navBar.setAutoHideController(autoHideController);
             navBar.restoreSystemUiVisibilityState();
             mNavigationBars.append(displayId, navBar);
@@ -231,11 +233,13 @@
     }
 
     /** @return {@link NavigationBarFragment} on the default display. */
+    @Nullable
     public NavigationBarFragment getDefaultNavigationBarFragment() {
         return mNavigationBars.get(DEFAULT_DISPLAY);
     }
 
     /** @return {@link AssistHandleViewController} (only on the default display). */
+    @Nullable
     public AssistHandleViewController getAssistHandlerViewController() {
         NavigationBarFragment navBar = getDefaultNavigationBarFragment();
         return navBar == null ? null : navBar.getAssistHandlerViewController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index a5b7fa7..f284f73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -31,6 +31,7 @@
 import android.app.PendingIntent;
 import android.app.SynchronousUserSwitchObserver;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
@@ -60,7 +61,7 @@
 
 import java.util.List;
 
-/** The clsss to show notification(s) of instant apps. This may show multiple notifications on
+/** The class to show notification(s) of instant apps. This may show multiple notifications on
  * splitted screen.
  */
 public class InstantAppNotifier extends SystemUI
@@ -74,7 +75,9 @@
     private boolean mDockedStackExists;
     private KeyguardStateController mKeyguardStateController;
 
-    public InstantAppNotifier() {}
+    public InstantAppNotifier(Context context) {
+        super(context);
+    }
 
     @Override
     public void start() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 2f67f90..8a23e37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -37,10 +37,10 @@
 
 @Singleton
 class NotificationWakeUpCoordinator @Inject constructor(
-        private val mContext: Context,
         private val mHeadsUpManagerPhone: HeadsUpManagerPhone,
         private val statusBarStateController: StatusBarStateController,
-        private val bypassController: KeyguardBypassController)
+        private val bypassController: KeyguardBypassController,
+        private val dozeParameters: DozeParameters)
     : OnHeadsUpChangedListener, StatusBarStateController.StateListener,
         PanelExpansionListener {
 
@@ -67,7 +67,6 @@
     private var mVisibilityAmount = 0.0f
     private var mLinearVisibilityAmount = 0.0f
     private val mEntrySetToClearWhenFinished = mutableSetOf<NotificationEntry>()
-    private val mDozeParameters: DozeParameters
     private var pulseExpanding: Boolean = false
     private val wakeUpListeners = arrayListOf<WakeUpListener>()
     private var state: Int = StatusBarState.KEYGUARD
@@ -146,7 +145,6 @@
     init {
         mHeadsUpManagerPhone.addListener(this)
         statusBarStateController.addCallback(this)
-        mDozeParameters = DozeParameters.getInstance(mContext)
         addListener(object : WakeUpListener {
             override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
                 if (isFullyHidden && mNotificationsVisibleForExpansion) {
@@ -377,7 +375,7 @@
     }
 
     private fun shouldAnimateVisibility() =
-            mDozeParameters.getAlwaysOn() && !mDozeParameters.getDisplayNeedsBlanking()
+            dozeParameters.getAlwaysOn() && !dozeParameters.getDisplayNeedsBlanking()
 
     interface WakeUpListener {
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 7bbe818..9817825 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -473,8 +473,7 @@
     private int mHeadsUpInset;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     private NotificationIconAreaController mIconAreaController;
-    private final NotificationLockscreenUserManager mLockscreenUserManager =
-            Dependency.get(NotificationLockscreenUserManager.class);
+    private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final Rect mTmpRect = new Rect();
     private final NotificationEntryManager mEntryManager =
             Dependency.get(NotificationEntryManager.class);
@@ -497,8 +496,7 @@
     private NotificationPanelView mNotificationPanel;
     private final ShadeController mShadeController = Dependency.get(ShadeController.class);
 
-    private final NotificationGutsManager
-            mNotificationGutsManager = Dependency.get(NotificationGutsManager.class);
+    private final NotificationGutsManager mNotificationGutsManager;
     private final NotificationSectionsManager mSectionsManager;
     private boolean mAnimateBottomOnLayout;
     private float mLastSentAppear;
@@ -518,6 +516,8 @@
             HeadsUpManagerPhone headsUpManager,
             KeyguardBypassController keyguardBypassController,
             FalsingManager falsingManager,
+            NotificationLockscreenUserManager notificationLockscreenUserManager,
+            NotificationGutsManager notificationGutsManager,
             NotificationSectionsFeatureManager sectionsFeatureManager) {
         super(context, attrs, 0, 0);
         Resources res = getResources();
@@ -526,6 +526,8 @@
 
         mRoundnessManager = notificationRoundnessManager;
 
+        mLockscreenUserManager = notificationLockscreenUserManager;
+        mNotificationGutsManager = notificationGutsManager;
         mHeadsUpManager = headsUpManager;
         mHeadsUpManager.addListener(mRoundnessManager);
         mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index 5912cd7..175d072 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -29,7 +29,6 @@
 import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -68,12 +67,14 @@
     };
 
     @Inject
-    public AutoHideController(Context context, @Named(MAIN_HANDLER_NAME) Handler handler) {
+    public AutoHideController(Context context, @Named(MAIN_HANDLER_NAME) Handler handler,
+            NotificationRemoteInputManager notificationRemoteInputManager,
+            IWindowManager iWindowManager) {
         mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class);
         mCommandQueue.addCallback(this);
         mHandler = handler;
-        mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
-        mWindowManagerService = Dependency.get(IWindowManager.class);
+        mRemoteInputManager = notificationRemoteInputManager;
+        mWindowManagerService = iWindowManager;
 
         mDisplayId = context.getDisplayId();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 7cbdfb0..548afd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -128,6 +128,7 @@
     private final KeyguardBypassController mKeyguardBypassController;
     private PowerManager.WakeLock mWakeLock;
     private final KeyguardUpdateMonitor mUpdateMonitor;
+    private final DozeParameters mDozeParameters;
     private final KeyguardStateController mKeyguardStateController;
     private final StatusBarWindowController mStatusBarWindowController;
     private final Context mContext;
@@ -146,31 +147,33 @@
 
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
-    public BiometricUnlockController(Context context,
+    public BiometricUnlockController(
+            Context context,
             DozeScrimController dozeScrimController,
             KeyguardViewMediator keyguardViewMediator,
             ScrimController scrimController,
             StatusBar statusBar,
             KeyguardStateController keyguardStateController, Handler handler,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            KeyguardBypassController keyguardBypassController) {
+            KeyguardBypassController keyguardBypassController,
+            DozeParameters dozeParameters) {
         this(context, dozeScrimController, keyguardViewMediator, scrimController, statusBar,
                 keyguardStateController, handler, keyguardUpdateMonitor,
                 context.getResources()
                         .getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze),
-                keyguardBypassController);
+                keyguardBypassController, dozeParameters);
     }
 
     @VisibleForTesting
-    protected BiometricUnlockController(Context context,
-            DozeScrimController dozeScrimController, KeyguardViewMediator keyguardViewMediator,
-            ScrimController scrimController, StatusBar statusBar,
-            KeyguardStateController keyguardStateController, Handler handler,
+    protected BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
+            KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
+            StatusBar statusBar, KeyguardStateController keyguardStateController, Handler handler,
             KeyguardUpdateMonitor keyguardUpdateMonitor, int wakeUpDelay,
-            KeyguardBypassController keyguardBypassController) {
+            KeyguardBypassController keyguardBypassController, DozeParameters dozeParameters) {
         mContext = context;
         mPowerManager = context.getSystemService(PowerManager.class);
         mUpdateMonitor = keyguardUpdateMonitor;
+        mDozeParameters = dozeParameters;
         mUpdateMonitor.registerCallback(this);
         mMediaManager = Dependency.get(NotificationMediaManager.class);
         Dependency.get(WakefulnessLifecycle.class).addObserver(mWakefulnessObserver);
@@ -284,7 +287,7 @@
         }
         // During wake and unlock, we need to draw black before waking up to avoid abrupt
         // brightness changes due to display state transitions.
-        boolean alwaysOnEnabled = DozeParameters.getInstance(mContext).getAlwaysOn();
+        boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
         boolean delayWakeUp = mode == MODE_WAKE_AND_UNLOCK && alwaysOnEnabled && mWakeUpDelay > 0;
         Runnable wakeUp = ()-> {
             if (!wasDeviceInteractive) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
index 0d62703..78ea5c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
@@ -100,6 +100,7 @@
                                     event.getY() - mActivationY);
                         }
                         if (withinDoubleTapSlop) {
+                            makeInactive();
                             if (!mDoubleTapListener.onDoubleTap()) {
                                 return false;
                             }
@@ -134,6 +135,7 @@
         if (mActivated) {
             mActivated = false;
             mActivationListener.onActiveChanged(false);
+            mView.removeCallbacks(mTapTimeoutRunnable);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index bb6a38e..28dac87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.PowerManager;
 import android.os.SystemProperties;
@@ -25,7 +25,7 @@
 import android.util.MathUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
+import com.android.systemui.DependencyProvider;
 import com.android.systemui.R;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
@@ -33,9 +33,13 @@
 
 import java.io.PrintWriter;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 /**
  * Retrieve doze information
  */
+@Singleton
 public class DozeParameters implements TunerService.Tunable,
         com.android.systemui.plugins.statusbar.DozeParameters {
     private static final int MAX_DURATION = 60 * 1000;
@@ -44,35 +48,33 @@
     public static final boolean FORCE_BLANKING =
             SystemProperties.getBoolean("debug.force_blanking", false);
 
-    private static DozeParameters sInstance;
-
-    private final Context mContext;
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     private final PowerManager mPowerManager;
 
     private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
+    private final Resources mResources;
 
     private boolean mDozeAlwaysOn;
     private boolean mControlScreenOffAnimation;
 
-    public static DozeParameters getInstance(Context context) {
-        if (sInstance == null) {
-            sInstance = new DozeParameters(context);
-        }
-        return sInstance;
-    }
-
-    @VisibleForTesting
-    protected DozeParameters(Context context) {
-        mContext = context.getApplicationContext();
-        mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
-        mAlwaysOnPolicy = new AlwaysOnDisplayPolicy(mContext);
+    @Inject
+    protected DozeParameters(
+            @DependencyProvider.MainResources Resources resources,
+            AmbientDisplayConfiguration ambientDisplayConfiguration,
+            AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
+            PowerManager powerManager,
+            TunerService tunerService) {
+        mResources = resources;
+        mAmbientDisplayConfiguration = ambientDisplayConfiguration;
+        mAlwaysOnPolicy = alwaysOnDisplayPolicy;
 
         mControlScreenOffAnimation = !getDisplayNeedsBlanking();
-        mPowerManager = mContext.getSystemService(PowerManager.class);
+        mPowerManager = powerManager;
         mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
 
-        Dependency.get(TunerService.class).addTunable(this, Settings.Secure.DOZE_ALWAYS_ON,
+        tunerService.addTunable(
+                this,
+                Settings.Secure.DOZE_ALWAYS_ON,
                 Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
     }
 
@@ -95,7 +97,7 @@
     }
 
     public boolean getDozeSuspendDisplayStateSupported() {
-        return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
+        return mResources.getBoolean(R.bool.doze_suspend_display_state_supported);
     }
 
     public int getPulseDuration() {
@@ -103,7 +105,7 @@
     }
 
     public float getScreenBrightnessDoze() {
-        return mContext.getResources().getInteger(
+        return mResources.getInteger(
                 com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
     }
 
@@ -173,7 +175,7 @@
      * @return {@code true} if screen needs to be completely black before a power transition.
      */
     public boolean getDisplayNeedsBlanking() {
-        return FORCE_BLANKING || !FORCE_NO_BLANKING && mContext.getResources().getBoolean(
+        return FORCE_BLANKING || !FORCE_NO_BLANKING && mResources.getBoolean(
                 com.android.internal.R.bool.config_displayBlanksAfterDoze);
     }
 
@@ -195,24 +197,20 @@
     }
 
     private boolean getBoolean(String propName, int resId) {
-        return SystemProperties.getBoolean(propName, mContext.getResources().getBoolean(resId));
+        return SystemProperties.getBoolean(propName, mResources.getBoolean(resId));
     }
 
     private int getInt(String propName, int resId) {
-        int value = SystemProperties.getInt(propName, mContext.getResources().getInteger(resId));
+        int value = SystemProperties.getInt(propName, mResources.getInteger(resId));
         return MathUtils.constrain(value, 0, MAX_DURATION);
     }
 
-    private String getString(String propName, int resId) {
-        return SystemProperties.get(propName, mContext.getString(resId));
-    }
-
     public int getPulseVisibleDurationExtended() {
         return 2 * getPulseVisibleDuration();
     }
 
     public boolean doubleTapReportsTouchCoordinates() {
-        return mContext.getResources().getBoolean(R.bool.doze_double_tap_reports_touch_coordinates);
+        return mResources.getBoolean(R.bool.doze_double_tap_reports_touch_coordinates);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index ca7c227..442c089 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -276,7 +276,7 @@
                             | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
                     PixelFormat.TRANSLUCENT);
             mEdgePanelLp.privateFlags |=
-                    WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+                    WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
             mEdgePanelLp.setTitle(TAG + mDisplayId);
             mEdgePanelLp.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
             mEdgePanelLp.windowAnimations = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
index deb314b..a784984 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
@@ -82,7 +82,7 @@
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(mDiameter, mDiameter,
                 mMargin, mMargin, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags,
                 PixelFormat.TRANSLUCENT);
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("FloatingRotationButton");
         switch (mWindowManager.getDefaultDisplay().getRotation()) {
             case Surface.ROTATION_0:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index da62d9b..ce96005 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -98,9 +98,10 @@
             View statusbarView,
             SysuiStatusBarStateController statusBarStateController,
             KeyguardBypassController keyguardBypassController,
+            KeyguardStateController keyguardStateController,
             NotificationWakeUpCoordinator wakeUpCoordinator) {
         this(notificationIconAreaController, headsUpManager, statusBarStateController,
-                keyguardBypassController, wakeUpCoordinator,
+                keyguardBypassController, wakeUpCoordinator, keyguardStateController,
                 statusbarView.findViewById(R.id.heads_up_status_bar_view),
                 statusbarView.findViewById(R.id.notification_stack_scroller),
                 statusbarView.findViewById(R.id.notification_panel),
@@ -116,6 +117,7 @@
             StatusBarStateController stateController,
             KeyguardBypassController bypassController,
             NotificationWakeUpCoordinator wakeUpCoordinator,
+            KeyguardStateController keyguardStateController,
             HeadsUpStatusBarView headsUpStatusBarView,
             NotificationStackScrollLayout stackScroller,
             NotificationPanelView panelView,
@@ -160,7 +162,7 @@
         mWakeUpCoordinator = wakeUpCoordinator;
         wakeUpCoordinator.addListener(this);
         mCommandQueue = getComponent(headsUpStatusBarView.getContext(), CommandQueue.class);
-        mKeyguardStateController = Dependency.get(KeyguardStateController.class);
+        mKeyguardStateController = keyguardStateController;
     }
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a7e7f08..c6d051d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -29,6 +29,7 @@
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
 
 import androidx.collection.ArraySet;
 
@@ -390,7 +391,12 @@
     }
 
     private void updateRegionForNotch(Region region) {
-        DisplayCutout cutout = mStatusBarWindowView.getRootWindowInsets().getDisplayCutout();
+        WindowInsets windowInsets = mStatusBarWindowView.getRootWindowInsets();
+        if (windowInsets == null) {
+            Log.w(TAG, "StatusBarWindowView is not attached.");
+            return;
+        }
+        DisplayCutout cutout = windowInsets.getDisplayCutout();
         if (cutout == null) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index cac3304..d95d2b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -132,7 +132,6 @@
 
     private ActivityStarter mActivityStarter;
     private KeyguardStateController mKeyguardStateController;
-    private LockPatternUtils mLockPatternUtils;
     private FlashlightController mFlashlightController;
     private PreviewInflater mPreviewInflater;
     private AccessibilityController mAccessibilityController;
@@ -231,7 +230,6 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mLockPatternUtils = new LockPatternUtils(mContext);
         mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext),
                 new ActivityIntentHelper(mContext));
         mPreviewContainer = findViewById(R.id.preview_container);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 9804f9f..ae18833 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -1212,8 +1212,11 @@
         setClipChildren(shouldClip);
         setClipToPadding(shouldClip);
 
-        AssistHandleViewController controller = Dependency.get(NavigationBarController.class)
-                .getAssistHandlerViewController();
+        NavigationBarController navigationBarController =
+                Dependency.get(NavigationBarController.class);
+        AssistHandleViewController controller =
+                navigationBarController == null
+                        ? null : navigationBarController.getAssistHandlerViewController();
         if (controller != null) {
             controller.setBottomOffset(insets.getSystemWindowInsetBottom());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 1a3560e..1a37520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -80,11 +80,14 @@
     private boolean mAodIconsVisible;
     private boolean mIsPulsing;
 
-    public NotificationIconAreaController(Context context, StatusBar statusBar,
+    public NotificationIconAreaController(
+            Context context,
+            StatusBar statusBar,
             StatusBarStateController statusBarStateController,
             NotificationWakeUpCoordinator wakeUpCoordinator,
             KeyguardBypassController keyguardBypassController,
-            NotificationMediaManager notificationMediaManager) {
+            NotificationMediaManager notificationMediaManager,
+            DozeParameters dozeParameters) {
         mStatusBar = statusBar;
         mContrastColorUtil = ContrastColorUtil.getInstance(context);
         mContext = context;
@@ -92,7 +95,7 @@
         mStatusBarStateController = statusBarStateController;
         mStatusBarStateController.addCallback(this);
         mMediaManager = notificationMediaManager;
-        mDozeParameters = DozeParameters.getInstance(mContext);
+        mDozeParameters = dozeParameters;
         mWakeUpCoordinator = wakeUpCoordinator;
         wakeUpCoordinator.addListener(this);
         mBypassController = keyguardBypassController;
@@ -533,8 +536,7 @@
     }
 
     public void appearAodIcons() {
-        DozeParameters dozeParameters = DozeParameters.getInstance(mContext);
-        if (dozeParameters.shouldControlScreenOff()) {
+        if (mDozeParameters.shouldControlScreenOff()) {
             mAodIcons.setTranslationY(-mAodIconAppearTranslation);
             mAodIcons.setAlpha(0);
             animateInAodIconTranslation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 00736b7..89051cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -43,6 +43,7 @@
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.MathUtils;
@@ -89,6 +90,7 @@
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -144,6 +146,7 @@
      * Fling until QS is completely hidden.
      */
     public static final int FLING_HIDE = 2;
+    private final DozeParameters mDozeParameters;
 
     private double mQqsSplitFraction;
 
@@ -452,17 +455,17 @@
     @Inject
     public NotificationPanelView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
             InjectionInflationController injectionInflationController,
-            NotificationWakeUpCoordinator coordinator,
-            PulseExpansionHandler pulseExpansionHandler,
+            NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
             DynamicPrivacyController dynamicPrivacyController,
-            KeyguardBypassController bypassController,
-            FalsingManager falsingManager,
-            PluginManager pluginManager,
-            ShadeController shadeController,
+            KeyguardBypassController bypassController, FalsingManager falsingManager,
+            PluginManager pluginManager, ShadeController shadeController,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
             NotificationEntryManager notificationEntryManager,
-            DozeLog dozeLog) {
-        super(context, attrs, falsingManager, dozeLog);
+            KeyguardStateController keyguardStateController,
+            StatusBarStateController statusBarStateController, DozeLog dozeLog,
+            DozeParameters dozeParameters) {
+        super(context, attrs, falsingManager, dozeLog, keyguardStateController,
+                (SysuiStatusBarStateController) statusBarStateController);
         setWillNotDraw(!DEBUG);
         mInjectionInflationController = injectionInflationController;
         mFalsingManager = falsingManager;
@@ -475,6 +478,7 @@
         mCommandQueue = getComponent(context, CommandQueue.class);
         mDisplayId = context.getDisplayId();
         mPulseExpansionHandler = pulseExpansionHandler;
+        mDozeParameters = dozeParameters;
         pulseExpansionHandler.setPulseExpandAbortListener(() -> {
             if (mQs != null) {
                 mQs.animateHeaderSlidingOut();
@@ -537,7 +541,10 @@
         mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
         mLastOrientation = getResources().getConfiguration().orientation;
         mPluginFrame = findViewById(R.id.plugin_frame);
-        mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+        if (Settings.System.getInt(
+                mContext.getContentResolver(), "npv_plugin_flag", 0) == 1) {
+            mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+        }
 
 
         initBottomArea();
@@ -774,7 +781,7 @@
             mPluginFrame.setLayoutParams(lp);
         }
 
-        mNPVPluginManager.replaceFrameLayout(mPluginFrame);
+        if (mNPVPluginManager != null) mNPVPluginManager.replaceFrameLayout(mPluginFrame);
     }
 
     private void initBottomArea() {
@@ -804,8 +811,10 @@
         int oldMaxHeight = mQsMaxExpansionHeight;
         if (mQs != null) {
             mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
-            mNPVPluginManager.setYOffset(mQsMinExpansionHeight);
-            mQsMinExpansionHeight += mNPVPluginManager.getHeight();
+            if (mNPVPluginManager != null) {
+                mNPVPluginManager.setYOffset(mQsMinExpansionHeight);
+                mQsMinExpansionHeight += mNPVPluginManager.getHeight();
+            }
             mQsMaxExpansionHeight = mQs.getDesiredHeight();
             mNotificationStackScroller.setMaxTopPadding(
                     mQsMaxExpansionHeight + mQsNotificationTopPadding);
@@ -1911,9 +1920,11 @@
                 mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
                         || mQsExpansionFromOverscroll));
         updateEmptyShadeView();
-        mNPVPluginManager.changeVisibility((mBarState != StatusBarState.KEYGUARD)
-                ? View.VISIBLE
-                : View.INVISIBLE);
+        if (mNPVPluginManager != null) {
+            mNPVPluginManager.changeVisibility((mBarState != StatusBarState.KEYGUARD)
+                    ? View.VISIBLE
+                    : View.INVISIBLE);
+        }
         mQsNavbarScrim.setVisibility(mBarState == StatusBarState.SHADE && mQsExpanded
                 && !mStackScrollerOverscrolling && mQsScrimEnabled
                 ? View.VISIBLE
@@ -1971,7 +1982,9 @@
         float qsExpansionFraction = getQsExpansionFraction();
         mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
         int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
-        mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
+        if (mNPVPluginManager != null) {
+            mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
+        }
         mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
     }
 
@@ -2392,7 +2405,7 @@
                 appearAmount = mNotificationStackScroller.calculateAppearFractionBypass();
             }
             startHeight = -mQs.getQsMinExpansionHeight();
-            startHeight -= mNPVPluginManager.getHeight();
+            if (mNPVPluginManager != null) startHeight -= mNPVPluginManager.getHeight();
         }
         float translation = MathUtils.lerp(startHeight, 0,
                 Math.min(1.0f, appearAmount))
@@ -2536,7 +2549,7 @@
         mKeyguardStatusBar.setListening(listening);
         if (mQs == null) return;
         mQs.setListening(listening);
-        mNPVPluginManager.setListening(listening);
+        if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening);
     }
 
     @Override
@@ -3421,9 +3434,8 @@
 
     public void setPulsing(boolean pulsing) {
         mPulsing = pulsing;
-        DozeParameters dozeParameters = DozeParameters.getInstance(mContext);
-        final boolean animatePulse = !dozeParameters.getDisplayNeedsBlanking()
-                && dozeParameters.getAlwaysOn();
+        final boolean animatePulse = !mDozeParameters.getDisplayNeedsBlanking()
+                && mDozeParameters.getAlwaysOn();
         if (animatePulse) {
             mAnimateNextPositionUpdate = true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 432d636..e8e5e1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -44,7 +44,6 @@
 import com.android.systemui.R;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -144,10 +143,8 @@
     private boolean mGestureWaitForTouchSlop;
     private boolean mIgnoreXTouchSlop;
     private boolean mExpandLatencyTracking;
-    protected final KeyguardStateController mKeyguardStateController = Dependency.get(
-            KeyguardStateController.class);
-    protected final SysuiStatusBarStateController mStatusBarStateController =
-            (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
+    protected final KeyguardStateController mKeyguardStateController;
+    protected final SysuiStatusBarStateController mStatusBarStateController;
 
     protected void onExpandingFinished() {
         mBar.onExpandingFinished();
@@ -206,8 +203,11 @@
     }
 
     public PanelView(Context context, AttributeSet attrs, FalsingManager falsingManager,
-            DozeLog dozeLog) {
+            DozeLog dozeLog, KeyguardStateController keyguardStateController,
+            SysuiStatusBarStateController statusBarStateController) {
         super(context, attrs);
+        mKeyguardStateController = keyguardStateController;
+        mStatusBarStateController = statusBarStateController;
         mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f /* maxLengthSeconds */,
                 0.6f /* speedUpFactor */);
         mFlingAnimationUtilsClosing = new FlingAnimationUtils(context, 0.5f /* maxLengthSeconds */,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 5ba19cc..e7d896c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -258,7 +258,7 @@
 
         final ScrimState oldState = mState;
         mState = state;
-        Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.getIndex());
+        Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.ordinal());
 
         if (mCallback != null) {
             mCallback.onCancelled();
@@ -519,22 +519,6 @@
     }
 
     /**
-     * Set front scrim to black, cancelling animations, in order to prepare to fade them
-     * away once the display turns on.
-     */
-    public void prepareForGentleWakeUp() {
-        if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()) {
-            mInFrontAlpha = 1f;
-            mInFrontTint = Color.BLACK;
-            mBehindTint = Color.BLACK;
-            mAnimateChange = false;
-            updateScrims();
-            mAnimateChange = true;
-            mAnimationDuration = ANIMATION_DURATION_LONG;
-        }
-    }
-
-    /**
      * If the lock screen sensor is active.
      */
     public void setWakeLockScreenSensorActive(boolean active) {
@@ -595,6 +579,8 @@
         setScrimAlpha(mScrimInFront, mInFrontAlpha);
         setScrimAlpha(mScrimBehind, mBehindAlpha);
         setScrimAlpha(mScrimForBubble, mBubbleAlpha);
+        // The animation could have all already finished, let's call onFinished just in case
+        onFinished();
         dispatchScrimsVisible();
     }
 
@@ -693,9 +679,9 @@
 
             @Override
             public void onAnimationEnd(Animator animation) {
+                scrim.setTag(TAG_KEY_ANIM, null);
                 onFinished(lastCallback);
 
-                scrim.setTag(TAG_KEY_ANIM, null);
                 dispatchScrimsVisible();
 
                 if (!mDeferFinishedListener && mOnAnimationFinished != null) {
@@ -759,9 +745,9 @@
     }
 
     private void onFinished(Callback callback) {
-        if (!hasReachedFinalState(mScrimBehind)
-            || !hasReachedFinalState(mScrimInFront)
-            || !hasReachedFinalState(mScrimForBubble)) {
+        if (isAnimating(mScrimBehind)
+            || isAnimating(mScrimInFront)
+            || isAnimating(mScrimForBubble)) {
             if (callback != null && callback != mCallback) {
                 // Since we only notify the callback that we're finished once everything has
                 // finished, we need to make sure that any changing callbacks are also invoked
@@ -794,11 +780,6 @@
         }
     }
 
-    private boolean hasReachedFinalState(ScrimView scrim) {
-        return scrim.getViewAlpha() == getCurrentScrimAlpha(scrim)
-                && scrim.getTint() == getCurrentScrimTint(scrim);
-    }
-
     private boolean isAnimating(View scrim) {
         return scrim.getTag(TAG_KEY_ANIM) != null;
     }
@@ -854,10 +835,7 @@
             } else {
                 // update the alpha directly
                 updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
-                onFinished();
             }
-        } else {
-            onFinished();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 7463c7c..e0597159 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -30,12 +30,30 @@
     /**
      * Initial state.
      */
-    UNINITIALIZED(-1),
+    UNINITIALIZED,
+
+    /**
+     * When turned off by sensors (prox, presence.)
+     */
+    OFF {
+        @Override
+        public void prepare(ScrimState previousState) {
+            mFrontTint = Color.BLACK;
+            mBehindTint = previousState.mBehindTint;
+            mBubbleTint = previousState.mBubbleTint;
+
+            mFrontAlpha = 1f;
+            mBehindAlpha = previousState.mBehindAlpha;
+            mBubbleAlpha = previousState.mBubbleAlpha;
+
+            mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
+        }
+    },
 
     /**
      * On the lock screen.
      */
-    KEYGUARD(0) {
+    KEYGUARD {
         @Override
         public void prepare(ScrimState previousState) {
             mBlankScreen = false;
@@ -65,7 +83,7 @@
     /**
      * Showing password challenge on the keyguard.
      */
-    BOUNCER(1) {
+    BOUNCER {
         @Override
         public void prepare(ScrimState previousState) {
             mBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
@@ -77,7 +95,7 @@
     /**
      * Showing password challenge on top of a FLAG_SHOW_WHEN_LOCKED activity.
      */
-    BOUNCER_SCRIMMED(2) {
+    BOUNCER_SCRIMMED {
         @Override
         public void prepare(ScrimState previousState) {
             mBehindAlpha = 0;
@@ -89,7 +107,7 @@
     /**
      * Changing screen brightness from quick settings.
      */
-    BRIGHTNESS_MIRROR(3) {
+    BRIGHTNESS_MIRROR {
         @Override
         public void prepare(ScrimState previousState) {
             mBehindAlpha = 0;
@@ -101,7 +119,7 @@
     /**
      * Always on display or screen off.
      */
-    AOD(4) {
+    AOD {
         @Override
         public void prepare(ScrimState previousState) {
             final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
@@ -136,7 +154,7 @@
     /**
      * When phone wakes up because you received a notification.
      */
-    PULSING(5) {
+    PULSING {
         @Override
         public void prepare(ScrimState previousState) {
             mFrontAlpha = mAodFrontScrimAlpha;
@@ -164,7 +182,7 @@
     /**
      * Unlocked on top of an app (launcher or any other activity.)
      */
-    UNLOCKED(6) {
+    UNLOCKED {
         @Override
         public void prepare(ScrimState previousState) {
             // State that UI will sync to.
@@ -201,7 +219,7 @@
     /**
      * Unlocked with a bubble expanded.
      */
-    BUBBLE_EXPANDED(7) {
+    BUBBLE_EXPANDED {
         @Override
         public void prepare(ScrimState previousState) {
             mFrontTint = Color.TRANSPARENT;
@@ -237,17 +255,12 @@
     DozeParameters mDozeParameters;
     boolean mDisplayRequiresBlanking;
     boolean mWallpaperSupportsAmbientMode;
-    int mIndex;
     boolean mHasBackdrop;
     boolean mLaunchingAffordanceWithPreview;
     boolean mWakeLockScreenSensorActive;
     boolean mKeyguardFadingAway;
     long mKeyguardFadingAwayDuration;
 
-    ScrimState(int index) {
-        mIndex = index;
-    }
-
     public void init(ScrimView scrimInFront, ScrimView scrimBehind, ScrimView scrimForBubble,
             DozeParameters dozeParameters) {
         mScrimInFront = scrimInFront;
@@ -262,10 +275,6 @@
     public void prepare(ScrimState previousState) {
     }
 
-    public int getIndex() {
-        return mIndex;
-    }
-
     public float getFrontAlpha() {
         return mFrontAlpha;
     }
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 2b80d22..c092f9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -387,6 +387,7 @@
     private final ConfigurationController mConfigurationController;
     private final StatusBarWindowViewController.Builder mStatusBarWindowViewControllerBuilder;
     private final NotifLog mNotifLog;
+    private final DozeParameters mDozeParameters;
 
     // expanded notifications
     protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
@@ -497,8 +498,7 @@
             WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
             final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
                     com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
-            final boolean imageWallpaperInAmbient =
-                    !DozeParameters.getInstance(mContext).getDisplayNeedsBlanking();
+            final boolean imageWallpaperInAmbient = !mDozeParameters.getDisplayNeedsBlanking();
             // If WallpaperInfo is null, it must be ImageWallpaper.
             final boolean supportsAmbientMode = deviceSupportsAodWallpaper
                     && ((info == null && imageWallpaperInAmbient)
@@ -620,6 +620,7 @@
 
     @Inject
     public StatusBar(
+            Context context,
             LightBarController lightBarController,
             AutoHideController autoHideController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -673,7 +674,9 @@
             ConfigurationController configurationController,
             StatusBarWindowController statusBarWindowController,
             StatusBarWindowViewController.Builder statusBarWindowViewControllerBuilder,
-            NotifLog notifLog) {
+            NotifLog notifLog,
+            DozeParameters dozeParameters) {
+        super(context);
         mLightBarController = lightBarController;
         mAutoHideController = autoHideController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -728,6 +731,7 @@
         mStatusBarWindowController = statusBarWindowController;
         mStatusBarWindowViewControllerBuilder = statusBarWindowViewControllerBuilder;
         mNotifLog = notifLog;
+        mDozeParameters = dozeParameters;
 
         mBubbleExpandListener =
                 (isExpanding, key) -> {
@@ -748,7 +752,7 @@
         KeyguardSliceProvider sliceProvider = KeyguardSliceProvider.getAttachedInstance();
         if (sliceProvider != null) {
             sliceProvider.initDependencies(mMediaManager, mStatusBarStateController,
-                    mKeyguardBypassController, DozeParameters.getInstance(mContext));
+                    mKeyguardBypassController, mDozeParameters);
         } else {
             Log.w(TAG, "Cannot init KeyguardSliceProvider dependencies");
         }
@@ -959,7 +963,7 @@
                     mHeadsUpAppearanceController = new HeadsUpAppearanceController(
                             mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow,
                             mStatusBarStateController, mKeyguardBypassController,
-                            mWakeUpCoordinator);
+                            mKeyguardStateController, mWakeUpCoordinator);
                     mHeadsUpAppearanceController.readFrom(oldController);
                     mStatusBarWindowViewController.setStatusBarView(mStatusBarView);
                     updateAreThereNotifications();
@@ -1030,13 +1034,12 @@
                     if (mStatusBarWindow != null) {
                         mStatusBarWindowViewController.onScrimVisibilityChanged(scrimsVisible);
                     }
-                }, DozeParameters.getInstance(mContext),
+                }, mDozeParameters,
                 mContext.getSystemService(AlarmManager.class),
                 mKeyguardStateController);
         mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf,
                 mHeadsUpManager, mNotificationIconAreaController, mScrimController);
-        mDozeScrimController = new DozeScrimController(DozeParameters.getInstance(context),
-                mDozeLog);
+        mDozeScrimController = new DozeScrimController(mDozeParameters, mDozeLog);
 
         BackDropView backdrop = mStatusBarWindow.findViewById(R.id.backdrop);
         mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
@@ -1171,7 +1174,7 @@
         mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanel,
                 mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController,
                 mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
-                mNotificationAlertingManager, rowBinder);
+                mNotificationAlertingManager, rowBinder, mKeyguardStateController);
 
         mNotificationListController =
                 new NotificationListController(
@@ -1341,7 +1344,7 @@
         mBiometricUnlockController = new BiometricUnlockController(mContext,
                 mDozeScrimController, mKeyguardViewMediator,
                 mScrimController, this, mKeyguardStateController, new Handler(),
-                mKeyguardUpdateMonitor, mKeyguardBypassController);
+                mKeyguardUpdateMonitor, mKeyguardBypassController, mDozeParameters);
         putComponent(BiometricUnlockController.class, mBiometricUnlockController);
         mStatusBarKeyguardViewManager = mKeyguardViewMediator.registerStatusBar(this,
                 getBouncerContainer(), mNotificationPanel, mBiometricUnlockController,
@@ -3569,8 +3572,7 @@
         mDozing = isDozing;
 
         // Collapse the notification panel if open
-        boolean dozingAnimated = mDozingRequested
-                && DozeParameters.getInstance(mContext).shouldControlScreenOff();
+        boolean dozingAnimated = mDozingRequested && mDozeParameters.shouldControlScreenOff();
         mNotificationPanel.resetViews(dozingAnimated);
 
         updateQsExpansionEnabled();
@@ -3600,7 +3602,6 @@
 
     private void updateKeyguardState() {
         mKeyguardStateController.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
-                mKeyguardStateController.isMethodSecure(),
                 mStatusBarKeyguardViewManager.isOccluded());
     }
 
@@ -3827,7 +3828,7 @@
      */
     private void updateNotificationPanelTouchState() {
         boolean goingToSleepWithoutAnimation = isGoingToSleep()
-                && !DozeParameters.getInstance(mContext).shouldControlScreenOff();
+                && !mDozeParameters.shouldControlScreenOff();
         boolean disabled = (!mDeviceInteractive && !mPulsing) || goingToSleepWithoutAnimation;
         mNotificationPanel.setTouchAndAnimationDisabled(disabled);
         mNotificationIconAreaController.setAnimationsEnabled(!disabled);
@@ -4017,6 +4018,13 @@
         } else if (isPulsing()) {
             mScrimController.transitionTo(ScrimState.PULSING,
                     mDozeScrimController.getScrimCallback());
+        } else if (mDozeServiceHost.hasPendingScreenOffCallback()) {
+            mScrimController.transitionTo(ScrimState.OFF, new ScrimController.Callback() {
+                @Override
+                public void onFinished() {
+                    mDozeServiceHost.executePendingScreenOffCallback();
+                }
+            });
         } else if (mDozing && !unlocking) {
             mScrimController.transitionTo(ScrimState.AOD);
         } else if (mIsKeyguard && !unlocking) {
@@ -4043,6 +4051,7 @@
         private boolean mAnimateWakeup;
         private boolean mAnimateScreenOff;
         private boolean mIgnoreTouchWhilePulsing;
+        private Runnable mPendingScreenOffCallback;
         @VisibleForTesting
         boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean(
                 "persist.sysui.wake_performs_auth", true);
@@ -4265,8 +4274,33 @@
         }
 
         @Override
-        public void prepareForGentleWakeUp() {
-            mScrimController.prepareForGentleWakeUp();
+        public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
+            if (onDisplayOffCallback != null) {
+                Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one.");
+            }
+            mPendingScreenOffCallback = onDisplayOffCallback;
+            updateScrimController();
+        }
+
+        /**
+         * When the dozing host is waiting for scrims to fade out to change the display state.
+         */
+        boolean hasPendingScreenOffCallback() {
+            return mPendingScreenOffCallback != null;
+        }
+
+        /**
+         * Executes an nullifies the pending display state callback.
+         *
+         * @see #hasPendingScreenOffCallback()
+         * @see #prepareForGentleSleep(Runnable)
+         */
+        void executePendingScreenOffCallback() {
+            if (mPendingScreenOffCallback == null) {
+                return;
+            }
+            mPendingScreenOffCallback.run();
+            mPendingScreenOffCallback = null;
         }
 
         private void dispatchTap(View view, float x, float y) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 75b0cdc..8683586 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -33,6 +33,8 @@
 import android.view.ViewRootImpl;
 import android.view.WindowManagerGlobal;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -61,8 +63,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
-import androidx.annotation.VisibleForTesting;
-
 /**
  * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
  * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -301,8 +301,7 @@
     public void show(Bundle options) {
         mShowing = true;
         mStatusBarWindowController.setKeyguardShowing(true);
-        mKeyguardStateController.notifyKeyguardState(
-                mShowing, mKeyguardStateController.isMethodSecure(),
+        mKeyguardStateController.notifyKeyguardState(mShowing,
                 mKeyguardStateController.isOccluded());
         reset(true /* hideBouncerWhenShowing */);
         StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
@@ -545,7 +544,7 @@
     public void hide(long startTime, long fadeoutDuration) {
         mShowing = false;
         mKeyguardStateController.notifyKeyguardState(mShowing,
-                mKeyguardStateController.isMethodSecure(), mKeyguardStateController.isOccluded());
+                mKeyguardStateController.isOccluded());
         launchPendingWakeupAction();
 
         if (Dependency.get(KeyguardUpdateMonitor.class).needsSlowUnlockTransition()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 3e0c268..f4a26ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -89,8 +89,7 @@
 
     private final ShadeController mShadeController = Dependency.get(ShadeController.class);
     private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
-    private final KeyguardStateController mKeyguardStateController = Dependency.get(
-            KeyguardStateController.class);
+    private final KeyguardStateController mKeyguardStateController;
     private final NotificationViewHierarchyManager mViewHierarchyManager =
             Dependency.get(NotificationViewHierarchyManager.class);
     private final NotificationLockscreenUserManager mLockscreenUserManager =
@@ -139,8 +138,10 @@
             ActivityLaunchAnimator activityLaunchAnimator,
             DynamicPrivacyController dynamicPrivacyController,
             NotificationAlertingManager notificationAlertingManager,
-            NotificationRowBinderImpl notificationRowBinder) {
+            NotificationRowBinderImpl notificationRowBinder,
+            KeyguardStateController keyguardStateController) {
         mContext = context;
+        mKeyguardStateController = keyguardStateController;
         mNotificationPanel = panel;
         mHeadsUpManager = headsUp;
         mDynamicPrivacyController = dynamicPrivacyController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 9a281ce..1def89b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -35,7 +35,6 @@
 import android.view.ViewParent;
 
 import com.android.systemui.ActivityIntentHelper;
-import com.android.systemui.Dependency;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
@@ -58,19 +57,16 @@
 public class StatusBarRemoteInputCallback implements Callback, Callbacks,
         StatusBarStateController.StateListener {
 
-    private final KeyguardStateController mKeyguardStateController = Dependency.get(
-            KeyguardStateController.class);
-    private final SysuiStatusBarStateController mStatusBarStateController =
-            (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
-    private final NotificationLockscreenUserManager mLockscreenUserManager =
-            Dependency.get(NotificationLockscreenUserManager.class);
-    private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
+    private final KeyguardStateController mKeyguardStateController;
+    private final SysuiStatusBarStateController mStatusBarStateController;
+    private final NotificationLockscreenUserManager mLockscreenUserManager;
+    private final ActivityStarter mActivityStarter;
+    private final ShadeController mShadeController;
     private final Context mContext;
     private final ActivityIntentHelper mActivityIntentHelper;
     private final NotificationGroupManager mGroupManager;
     private View mPendingWorkRemoteInputView;
     private View mPendingRemoteInputView;
-    private final ShadeController mShadeController = Dependency.get(ShadeController.class);
     private KeyguardManager mKeyguardManager;
     private final CommandQueue mCommandQueue;
     private int mDisabled2;
@@ -80,10 +76,19 @@
     /**
      */
     @Inject
-    public StatusBarRemoteInputCallback(Context context, NotificationGroupManager groupManager) {
+    public StatusBarRemoteInputCallback(Context context, NotificationGroupManager groupManager,
+            NotificationLockscreenUserManager notificationLockscreenUserManager,
+            KeyguardStateController keyguardStateController,
+            StatusBarStateController statusBarStateController,
+            ActivityStarter activityStarter, ShadeController shadeController) {
         mContext = context;
         mContext.registerReceiverAsUser(mChallengeReceiver, UserHandle.ALL,
                 new IntentFilter(ACTION_DEVICE_LOCKED_CHANGED), null, null);
+        mLockscreenUserManager = notificationLockscreenUserManager;
+        mKeyguardStateController = keyguardStateController;
+        mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
+        mShadeController = shadeController;
+        mActivityStarter = activityStarter;
         mStatusBarStateController.addCallback(this);
         mKeyguardManager = context.getSystemService(KeyguardManager.class);
         mCommandQueue = getComponent(context, CommandQueue.class);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index 724b462..ca7a936 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -21,7 +21,6 @@
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
 
-import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -39,8 +38,6 @@
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -94,24 +91,14 @@
     private final ArrayList<WeakReference<StatusBarWindowCallback>>
             mCallbacks = Lists.newArrayList();
 
-    private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
+    private final SysuiColorExtractor mColorExtractor;
 
     @Inject
-    public StatusBarWindowController(Context context,
-            StatusBarStateController statusBarStateController,
-            ConfigurationController configurationController,
-            KeyguardBypassController keyguardBypassController) {
-        this(context, context.getSystemService(WindowManager.class), ActivityManager.getService(),
-                DozeParameters.getInstance(context), statusBarStateController,
-                configurationController, keyguardBypassController);
-    }
-
-    @VisibleForTesting
     public StatusBarWindowController(Context context, WindowManager windowManager,
             IActivityManager activityManager, DozeParameters dozeParameters,
             StatusBarStateController statusBarStateController,
             ConfigurationController configurationController,
-            KeyguardBypassController keyguardBypassController) {
+            KeyguardBypassController keyguardBypassController, SysuiColorExtractor colorExtractor) {
         mContext = context;
         mWindowManager = windowManager;
         mActivityManager = activityManager;
@@ -120,6 +107,7 @@
         mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
         mLpChanged = new LayoutParams();
         mKeyguardBypassController = keyguardBypassController;
+        mColorExtractor = colorExtractor;
         mLockScreenDisplayTimeout = context.getResources()
                 .getInteger(R.integer.config_lockScreenDisplayTimeout);
         ((SysuiStatusBarStateController) statusBarStateController)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
index f21085e..fd3f9c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
@@ -37,14 +37,17 @@
 import com.android.systemui.R;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.InjectionInflationController;
 
@@ -88,7 +91,10 @@
             ShadeController shadeController,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
             NotificationEntryManager notificationEntryManager,
-            DozeLog dozeLog) {
+            KeyguardStateController keyguardStateController,
+            SysuiStatusBarStateController statusBarStateController,
+            DozeLog dozeLog,
+            DozeParameters dozeParameters) {
         mView = view;
         mFalsingManager = falsingManager;
 
@@ -106,7 +112,10 @@
                 shadeController,
                 notificationLockscreenUserManager,
                 notificationEntryManager,
-                dozeLog);
+                keyguardStateController,
+                statusBarStateController,
+                dozeLog,
+                dozeParameters);
         ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
         notificationPanelView.setVisibility(View.INVISIBLE);
@@ -472,10 +481,13 @@
         private final FalsingManager mFalsingManager;
         private final PluginManager mPluginManager;
         private final TunerService mTunerService;
+        private final KeyguardStateController mKeyguardStateController;
+        private final SysuiStatusBarStateController mStatusBarStateController;
         private ShadeController mShadeController;
         private final NotificationLockscreenUserManager mNotificationLockScreenUserManager;
         private final NotificationEntryManager mNotificationEntryManager;
         private final DozeLog mDozeLog;
+        private final DozeParameters mDozeParameters;
         private StatusBarWindowView mView;
 
         @Inject
@@ -490,7 +502,10 @@
                 TunerService tunerService,
                 NotificationLockscreenUserManager notificationLockscreenUserManager,
                 NotificationEntryManager notificationEntryManager,
-                DozeLog dozeLog) {
+                KeyguardStateController keyguardStateController,
+                StatusBarStateController statusBarStateController,
+                DozeLog dozeLog,
+                DozeParameters dozeParameters) {
             mInjectionInflationController = injectionInflationController;
             mCoordinator = coordinator;
             mPulseExpansionHandler = pulseExpansionHandler;
@@ -501,7 +516,10 @@
             mTunerService = tunerService;
             mNotificationLockScreenUserManager = notificationLockscreenUserManager;
             mNotificationEntryManager = notificationEntryManager;
+            mKeyguardStateController = keyguardStateController;
+            mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
             mDozeLog = dozeLog;
+            mDozeParameters = dozeParameters;
         }
 
         /**
@@ -537,7 +555,10 @@
                     mShadeController,
                     mNotificationLockScreenUserManager,
                     mNotificationEntryManager,
-                    mDozeLog);
+                    mKeyguardStateController,
+                    mStatusBarStateController,
+                    mDozeLog,
+                    mDozeParameters);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index ce929b7..44be6bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -93,10 +93,10 @@
     public static void setShowForAllUsers(Dialog dialog, boolean show) {
         if (show) {
             dialog.getWindow().getAttributes().privateFlags |=
-                    WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+                    WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         } else {
             dialog.getWindow().getAttributes().privateFlags &=
-                    ~WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+                    ~WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index aefe201..692c34c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -146,7 +146,7 @@
     /** **/
     default void notifyKeyguardDoneFading() {}
     /** **/
-    default void notifyKeyguardState(boolean showing, boolean methodSecure, boolean occluded) {}
+    default void notifyKeyguardState(boolean showing, boolean occluded) {}
 
     /**
      * Callback for authentication events.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 392094d..1cb2bd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -25,11 +25,12 @@
 import android.os.Build;
 import android.os.Trace;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 
 import java.io.FileDescriptor;
@@ -42,25 +43,22 @@
 /**
  */
 @Singleton
-public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback
-        implements KeyguardStateController, Dumpable {
+public class KeyguardStateControllerImpl implements KeyguardStateController, Dumpable {
 
     private static final boolean DEBUG_AUTH_WITH_ADB = false;
     private static final String AUTH_BROADCAST_KEY = "debug_trigger_auth";
 
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
-    private final Context mContext;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final LockPatternUtils mLockPatternUtils;
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
-            new LockedStateInvalidator();
+            new UpdateMonitorCallback();
 
     private boolean mCanDismissLockScreen;
     private boolean mShowing;
     private boolean mSecure;
     private boolean mOccluded;
 
-    private boolean mListening;
     private boolean mKeyguardFadingAway;
     private long mKeyguardFadingAwayDelay;
     private long mKeyguardFadingAwayDuration;
@@ -75,10 +73,10 @@
     /**
      */
     @Inject
-    public KeyguardStateControllerImpl(Context context) {
-        mContext = context;
-        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
-        mLockPatternUtils = new LockPatternUtils(context);
+    public KeyguardStateControllerImpl(Context context,
+            KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) {
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mLockPatternUtils = lockPatternUtils;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
 
         update(true /* updateAlways */);
@@ -104,19 +102,12 @@
         if (!mCallbacks.contains(callback)) {
             mCallbacks.add(callback);
         }
-        if (mCallbacks.size() != 0 && !mListening) {
-            mListening = true;
-            mKeyguardUpdateMonitor.registerCallback(this);
-        }
     }
 
     @Override
     public void removeCallback(@NonNull Callback callback) {
         Preconditions.checkNotNull(callback, "Callback must not be null. b/128895449");
-        if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
-            mListening = false;
-            mKeyguardUpdateMonitor.removeCallback(this);
-        }
+        mCallbacks.remove(callback);
     }
 
     @Override
@@ -140,19 +131,13 @@
     }
 
     @Override
-    public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
-        if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
+    public void notifyKeyguardState(boolean showing, boolean occluded) {
+        if (mShowing == showing && mOccluded == occluded) return;
         mShowing = showing;
-        mSecure = secure;
         mOccluded = occluded;
         notifyKeyguardChanged();
     }
 
-    @Override
-    public void onTrustChanged(int userId) {
-        notifyKeyguardChanged();
-    }
-
     private void notifyKeyguardChanged() {
         Trace.beginSection("KeyguardStateController#notifyKeyguardChanged");
         // Copy the list to allow removal during callback.
@@ -191,7 +176,8 @@
         setKeyguardFadingAway(false);
     }
 
-    private void update(boolean updateAlways) {
+    @VisibleForTesting
+    void update(boolean updateAlways) {
         Trace.beginSection("KeyguardStateController#update");
         int user = KeyguardUpdateMonitor.getCurrentUser();
         boolean secure = mLockPatternUtils.isSecure(user);
@@ -201,7 +187,7 @@
         boolean trusted = mKeyguardUpdateMonitor.getUserHasTrust(user);
         boolean faceAuthEnabled = mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(user);
         boolean changed = secure != mSecure || canDismissLockScreen != mCanDismissLockScreen
-                || trustManaged != mTrustManaged
+                || trustManaged != mTrustManaged || mTrusted != trusted
                 || mFaceAuthEnabled != faceAuthEnabled;
         if (changed || updateAlways) {
             mSecure = secure;
@@ -284,7 +270,7 @@
         pw.println("  mFaceAuthEnabled: " + mFaceAuthEnabled);
     }
 
-    private class LockedStateInvalidator extends KeyguardUpdateMonitorCallback {
+    private class UpdateMonitorCallback extends KeyguardUpdateMonitorCallback {
         @Override
         public void onUserSwitchComplete(int userId) {
             update(false /* updateAlways */);
@@ -293,6 +279,7 @@
         @Override
         public void onTrustChanged(int userId) {
             update(false /* updateAlways */);
+            notifyKeyguardChanged();
         }
 
         @Override
@@ -327,11 +314,6 @@
         }
 
         @Override
-        public void onScreenTurnedOff() {
-            update(false /* updateAlways */);
-        }
-
-        @Override
         public void onKeyguardVisibilityChanged(boolean showing) {
             update(false /* updateAlways */);
         }
@@ -340,5 +322,5 @@
         public void onBiometricsCleared() {
             update(false /* alwaysUpdate */);
         }
-    };
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 43795dc..cca9479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -23,12 +23,16 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
+import android.content.ClipDescription;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.ShortcutManager;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.Bundle;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.Editable;
@@ -53,8 +57,13 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import androidx.core.view.inputmethod.EditorInfoCompat;
+import androidx.core.view.inputmethod.InputConnectionCompat;
+import androidx.core.view.inputmethod.InputContentInfoCompat;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -65,6 +74,7 @@
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.LightBarController;
 
+import java.util.HashMap;
 import java.util.function.Consumer;
 
 /**
@@ -88,6 +98,8 @@
     private RemoteInputController mController;
     private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
 
+    private IStatusBarService mStatusBarManagerService;
+
     private NotificationEntry mEntry;
 
     private boolean mRemoved;
@@ -103,6 +115,8 @@
     public RemoteInputView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mRemoteInputQuickSettingsDisabler = Dependency.get(RemoteInputQuickSettingsDisabler.class);
+        mStatusBarManagerService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
     }
 
     @Override
@@ -128,7 +142,7 @@
 
                 if (isSoftImeEvent || isKeyboardEnterKey) {
                     if (mEditText.length() > 0) {
-                        sendRemoteInput();
+                        sendRemoteInput(prepareRemoteInputFromText());
                     }
                     // Consume action to prevent IME from closing.
                     return true;
@@ -141,7 +155,7 @@
         mEditText.mRemoteInputView = this;
     }
 
-    private void sendRemoteInput() {
+    protected Intent prepareRemoteInputFromText() {
         Bundle results = new Bundle();
         results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
         Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -153,6 +167,25 @@
             RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE);
         }
 
+        return fillInIntent;
+    }
+
+    protected Intent prepareRemoteInputFromData(String contentType, Uri data) {
+        HashMap<String, Uri> results = new HashMap<>();
+        results.put(contentType, data);
+        try {
+            mStatusBarManagerService.grantInlineReplyUriPermission(
+                    mEntry.notification.getKey(), data);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to grant URI permissions:" + e.getMessage(), e);
+        }
+        Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        RemoteInput.addDataResultToIntent(mRemoteInput, fillInIntent, results);
+
+        return fillInIntent;
+    }
+
+    private void sendRemoteInput(Intent intent) {
         mEditText.setEnabled(false);
         mSendButton.setVisibility(INVISIBLE);
         mProgressBar.setVisibility(VISIBLE);
@@ -176,7 +209,7 @@
         MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_SEND,
                 mEntry.notification.getPackageName());
         try {
-            mPendingIntent.send(mContext, 0, fillInIntent);
+            mPendingIntent.send(mContext, 0, intent);
         } catch (PendingIntent.CanceledException e) {
             Log.i(TAG, "Unable to send remote input result", e);
             MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_FAIL,
@@ -195,7 +228,9 @@
                 LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
         v.mController = controller;
         v.mEntry = entry;
-        v.mEditText.setTextOperationUser(computeTextOperationUser(entry.notification.getUser()));
+        UserHandle user = computeTextOperationUser(entry.notification.getUser());
+        v.mEditText.mUser = user;
+        v.mEditText.setTextOperationUser(user);
         v.setTag(VIEW_TAG);
 
         return v;
@@ -204,7 +239,7 @@
     @Override
     public void onClick(View v) {
         if (v == mSendButton) {
-            sendRemoteInput();
+            sendRemoteInput(prepareRemoteInputFromText());
         }
     }
 
@@ -518,6 +553,7 @@
         private RemoteInputView mRemoteInputView;
         boolean mShowImeOnInputConnection;
         private LightBarController mLightBarController;
+        UserHandle mUser;
 
         public RemoteEditText(Context context, AttributeSet attrs) {
             super(context, attrs);
@@ -617,11 +653,47 @@
 
         @Override
         public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+            String[] allowedDataTypes = mRemoteInputView.mRemoteInput.getAllowedDataTypes()
+                    .toArray(new String[0]);
+            EditorInfoCompat.setContentMimeTypes(outAttrs, allowedDataTypes);
             final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
 
-            if (mShowImeOnInputConnection && inputConnection != null) {
+            final InputConnectionCompat.OnCommitContentListener callback =
+                    new InputConnectionCompat.OnCommitContentListener() {
+                        @Override
+                        public boolean onCommitContent(
+                                InputContentInfoCompat inputContentInfoCompat, int i,
+                                Bundle bundle) {
+                            Uri contentUri = inputContentInfoCompat.getContentUri();
+                            ClipDescription description = inputContentInfoCompat.getDescription();
+                            String mimeType = null;
+                            if (description != null && description.getMimeTypeCount() > 0) {
+                                mimeType = description.getMimeType(0);
+                            }
+                            if (mimeType != null) {
+                                Intent dataIntent = mRemoteInputView.prepareRemoteInputFromData(
+                                        mimeType, contentUri);
+                                mRemoteInputView.sendRemoteInput(dataIntent);
+                            }
+                            return true;
+                        }
+                    };
+
+            InputConnection ic = InputConnectionCompat.createWrapper(
+                    inputConnection, outAttrs, callback);
+
+            Context userContext = null;
+            try {
+                userContext = mContext.createPackageContextAsUser(
+                        mContext.getPackageName(), 0, mUser);
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "Unable to create user context:" + e.getMessage(), e);
+            }
+
+            if (mShowImeOnInputConnection && ic != null) {
+                Context targetContext = userContext != null ? userContext : getContext();
                 final InputMethodManager imm =
-                        getContext().getSystemService(InputMethodManager.class);
+                        targetContext.getSystemService(InputMethodManager.class);
                 if (imm != null) {
                     // onCreateInputConnection is called by InputMethodManager in the middle of
                     // setting up the connection to the IME; wait with requesting the IME until that
@@ -636,7 +708,7 @@
                 }
             }
 
-            return inputConnection;
+            return ic;
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS
new file mode 100644
index 0000000..a601e9b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS
@@ -0,0 +1,8 @@
+# Android TV Core Framework
+rgl@google.com
+valiiftime@google.com
+galinap@google.com
+patrikf@google.com
+robhor@google.com
+sergeynv@google.com
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index b80b6d5..c2ed7df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -36,6 +36,10 @@
  */
 public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks {
 
+    public TvStatusBar(Context context) {
+        super(context);
+    }
+
     @Override
     public void start() {
         putComponent(TvStatusBar.class, this);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 89aa797..9a58a35 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -61,6 +61,10 @@
     private ThemeOverlayManager mThemeManager;
     private UserManager mUserManager;
 
+    public ThemeOverlayController(Context context) {
+        super(context);
+    }
+
     @Override
     public void start() {
         if (DEBUG) Log.d(TAG, "Start");
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index ff5bd03..11885c5 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -60,6 +60,10 @@
     private NotificationManager mNotificationManager;
     private StorageManager mStorageManager;
 
+    public StorageNotification(Context context) {
+        super(context);
+    }
+
     private static class MoveInfo {
         public int moveId;
         public Bundle extras;
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
index 0a3e34e..fd99ef3 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.hardware.usb.IUsbManager;
@@ -63,6 +64,7 @@
         mDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
         mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
         mResolveInfo = (ResolveInfo) intent.getParcelableExtra("rinfo");
+        String packageName = intent.getStringExtra(UsbManager.EXTRA_PACKAGE);
 
         PackageManager packageManager = getPackageManager();
         String appName = mResolveInfo.loadLabel(packageManager).toString();
@@ -74,8 +76,20 @@
                     mAccessory.getDescription());
             mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
         } else {
-            ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName,
-                    mDevice.getProductName());
+            int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+            boolean hasRecordPermission =
+                    PermissionChecker.checkPermissionForPreflight(
+                            this, android.Manifest.permission.RECORD_AUDIO, -1, uid,
+                            packageName)
+                            == android.content.pm.PackageManager.PERMISSION_GRANTED;
+            boolean isAudioCaptureDevice = mDevice.getHasAudioCapture();
+            boolean useRecordWarning = isAudioCaptureDevice && !hasRecordPermission;
+
+            int strID = useRecordWarning
+                    ? R.string.usb_device_confirm_prompt_warn
+                    : R.string.usb_device_confirm_prompt;
+
+            ap.mMessage = getString(strID, appName, mDevice.getProductName());
             mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
         }
         ap.mPositiveButtonText = getString(android.R.string.ok);
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index f35af90..8c60747 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -38,6 +38,10 @@
     public static String BATTERY     = "BAT";
     public static String HINTS       = "HNT";
 
+    public NotificationChannels(Context context) {
+        super(context);
+    }
+
     public static void createAll(Context context) {
         final NotificationManager nm = context.getSystemService(NotificationManager.class);
         final NotificationChannel batteryChannel = new NotificationChannel(BATTERY,
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index 2d5ebc4..63db755 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -526,11 +526,13 @@
     }
 
     /** */
+    @Singleton
     public static class Service extends SystemUI implements Dumpable {
         private final GarbageMonitor mGarbageMonitor;
 
         @Inject
-        public Service(GarbageMonitor garbageMonitor) {
+        public Service(Context context, GarbageMonitor garbageMonitor) {
+            super(context);
             mGarbageMonitor = garbageMonitor;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index dcd0c58..2224c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -139,6 +139,12 @@
 
     @Override
     protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener cannot be null");
+        }
+        if (sensor == null) {
+            throw new IllegalArgumentException("sensor cannot be null");
+        }
         mHandler.post(() -> {
             if (!mInner.requestTriggerSensor(listener, sensor)) {
                 Log.e(TAG, "Requesting " + listener + " for " + sensor + " failed.");
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index d2f185a..25a5139 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -27,7 +27,6 @@
 
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.Dependency;
-import com.android.systemui.SystemUI;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.PluginDependencyProvider;
@@ -40,9 +39,13 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 /**
  * Implementation of VolumeComponent backed by the new volume dialog.
  */
+@Singleton
 public class VolumeDialogComponent implements VolumeComponent, TunerService.Tunable,
         VolumeDialogControllerImpl.UserActivityListener{
 
@@ -54,12 +57,12 @@
     public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false;
     public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false;
 
-    private final SystemUI mSysui;
     protected final Context mContext;
     private final VolumeDialogControllerImpl mController;
     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
             ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
             | ActivityInfo.CONFIG_ASSETS_PATHS | ActivityInfo.CONFIG_UI_MODE);
+    private final KeyguardViewMediator mKeyguardViewMediator;
     private VolumeDialog mDialog;
     private VolumePolicy mVolumePolicy = new VolumePolicy(
             DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT,  // volumeDownToEnterSilent
@@ -68,9 +71,10 @@
             400    // vibrateToSilentDebounce
     );
 
-    public VolumeDialogComponent(SystemUI sysui, Context context) {
-        mSysui = sysui;
+    @Inject
+    public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator) {
         mContext = context;
+        mKeyguardViewMediator = keyguardViewMediator;
         mController = (VolumeDialogControllerImpl) Dependency.get(VolumeDialogController.class);
         mController.setUserActivityListener(this);
         // Allow plugins to reference the VolumeDialogController.
@@ -133,10 +137,7 @@
 
     @Override
     public void onUserActivity() {
-        final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class);
-        if (kvm != null) {
-            kvm.userActivity();
-        }
+        mKeyguardViewMediator.userActivity();
     }
 
     private void applyConfiguration() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index f8cf793..b7431397 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -16,18 +16,22 @@
 
 package com.android.systemui.volume;
 
+import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Handler;
 import android.util.Log;
 
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.qs.tiles.DndTile;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
 public class VolumeUI extends SystemUI {
     private static final String TAG = "VolumeUI";
     private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
@@ -37,6 +41,12 @@
     private boolean mEnabled;
     private VolumeDialogComponent mVolumeComponent;
 
+    @Inject
+    public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) {
+        super(context);
+        mVolumeComponent = volumeDialogComponent;
+    }
+
     @Override
     public void start() {
         boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
@@ -45,8 +55,6 @@
         mEnabled = enableVolumeUi || enableSafetyWarning;
         if (!mEnabled) return;
 
-        mVolumeComponent = SystemUIFactory.getInstance()
-                .createVolumeDialogComponent(this, mContext);
         mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning);
         putComponent(VolumeComponent.class, getVolumeComponent());
         setDefaultVolumeController();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index 38537fd..1dd4863 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -144,7 +144,7 @@
 
         mCarrierTextCallbackInfo = new CarrierTextController.CarrierTextCallbackInfo("",
                 new CharSequence[]{}, false, new int[]{});
-        when(mTelephonyManager.getMaxPhoneCount()).thenReturn(3);
+        when(mTelephonyManager.getSupportedModemCount()).thenReturn(3);
 
         mCarrierTextController = new CarrierTextController(mContext, SEPARATOR, true, true);
         // This should not start listening on any of the real dependencies but will test that
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index a07f25a..364ee66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -30,6 +30,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.util.Assert;
@@ -50,9 +51,10 @@
     @Before
     public void setUp() throws Exception {
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
+        mDependency.injectMockDependency(NotificationMediaManager.class);
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
         Context context = getContext();
-        mRow = new NotificationTestHelper(context).createRow();
+        mRow = new NotificationTestHelper(context, mDependency).createRow();
         mCallback = mock(ExpandHelper.Callback.class);
         mExpandHelper = new ExpandHelper(context, mCallback, 10, 100);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 64ab060..c338d70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -102,7 +102,7 @@
         when(mFragmentService.getFragmentHostManager(any())).thenReturn(mFragmentHostManager);
 
 
-        mScreenDecorations = new ScreenDecorations() {
+        mScreenDecorations = new ScreenDecorations(mContext) {
             @Override
             public void start() {
                 super.start();
@@ -126,7 +126,6 @@
                 mTestableLooper.processAllMessages();
             }
         };
-        mScreenDecorations.mContext = mContext;
         mScreenDecorations.mComponents = mContext.getComponents();
         reset(mTunerService);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
index 3ea7150..06999bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
@@ -58,13 +58,12 @@
         MockitoAnnotations.initMocks(this);
         doReturn(true).when(mMockButton).show();
 
-        mController = new SizeCompatModeActivityController(mMockAm) {
+        mController = new SizeCompatModeActivityController(mContext, mMockAm) {
             @Override
             RestartActivityButton createRestartButton(Context context) {
                 return mMockButton;
             };
         };
-        mController.mContext = mContext;
 
         ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
                 ArgumentCaptor.forClass(TaskStackChangeListener.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
index 19e1a5c..a766885 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
@@ -35,6 +35,7 @@
 
 import com.android.settingslib.SliceBroadcastRelay;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -44,6 +45,14 @@
 public class SliceBroadcastRelayHandlerTest extends SysuiTestCase {
 
     private static final String TEST_ACTION = "com.android.systemui.action.TEST_ACTION";
+    private SliceBroadcastRelayHandler mRelayHandler;
+    private Context mSpyContext;
+    @Before
+    public void setup() {
+        mSpyContext = spy(mContext);
+
+        mRelayHandler = new SliceBroadcastRelayHandler(mSpyContext);
+    }
 
     @Test
     public void testRegister() {
@@ -52,8 +61,6 @@
                 .authority("something")
                 .path("test")
                 .build();
-        SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler();
-        relayHandler.mContext = spy(mContext);
 
         Intent intent = new Intent(SliceBroadcastRelay.ACTION_REGISTER);
         intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
@@ -63,8 +70,8 @@
         intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value);
         intent.putExtra(SliceBroadcastRelay.EXTRA_URI, testUri);
 
-        relayHandler.handleIntent(intent);
-        verify(relayHandler.mContext).registerReceiver(any(), eq(value));
+        mRelayHandler.handleIntent(intent);
+        verify(mSpyContext).registerReceiver(any(), eq(value));
     }
 
     @Test
@@ -74,8 +81,6 @@
                 .authority("something")
                 .path("test")
                 .build();
-        SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler();
-        relayHandler.mContext = spy(mContext);
 
         Intent intent = new Intent(SliceBroadcastRelay.ACTION_REGISTER);
         intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
@@ -84,14 +89,14 @@
         IntentFilter value = new IntentFilter(TEST_ACTION);
         intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value);
 
-        relayHandler.handleIntent(intent);
+        mRelayHandler.handleIntent(intent);
         ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(relayHandler.mContext).registerReceiver(relay.capture(), eq(value));
+        verify(mSpyContext).registerReceiver(relay.capture(), eq(value));
 
         intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER);
         intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
-        relayHandler.handleIntent(intent);
-        verify(relayHandler.mContext).unregisterReceiver(eq(relay.getValue()));
+        mRelayHandler.handleIntent(intent);
+        verify(mSpyContext).unregisterReceiver(eq(relay.getValue()));
     }
 
     @Test
@@ -101,12 +106,10 @@
                 .authority("something")
                 .path("test")
                 .build();
-        SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler();
-        relayHandler.mContext = spy(mContext);
 
         Intent intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER);
         intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
-        relayHandler.handleIntent(intent);
+        mRelayHandler.handleIntent(intent);
         // No crash
     }
 
@@ -118,9 +121,6 @@
                 .authority("something")
                 .path("test")
                 .build();
-        SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler();
-        relayHandler.mContext = spy(mContext);
-
         Intent intent = new Intent(SliceBroadcastRelay.ACTION_REGISTER);
         intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
         intent.putExtra(SliceBroadcastRelay.EXTRA_RECEIVER,
@@ -128,10 +128,10 @@
         IntentFilter value = new IntentFilter(TEST_ACTION);
         intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value);
 
-        relayHandler.handleIntent(intent);
+        mRelayHandler.handleIntent(intent);
         ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(relayHandler.mContext).registerReceiver(relay.capture(), eq(value));
-        relay.getValue().onReceive(relayHandler.mContext, new Intent(TEST_ACTION));
+        verify(mSpyContext).registerReceiver(relay.capture(), eq(value));
+        relay.getValue().onReceive(mSpyContext, new Intent(TEST_ACTION));
 
         verify(Receiver.sReceiver, timeout(2000)).onReceive(any(), any());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index dcdb5c3..e1eb3b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -34,6 +34,7 @@
 import android.app.ActivityManager;
 import android.app.IActivityTaskManager;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.hardware.biometrics.Authenticator;
@@ -98,8 +99,7 @@
         when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
         when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
 
-        mAuthController = new TestableAuthController(new MockInjector());
-        mAuthController.mContext = context;
+        mAuthController = new TestableAuthController(context, new MockInjector());
         mAuthController.mComponents = mContext.getComponents();
 
         mAuthController.start();
@@ -404,8 +404,8 @@
         private int mBuildCount = 0;
         private Bundle mLastBiometricPromptBundle;
 
-        public TestableAuthController(Injector injector) {
-            super(injector);
+        TestableAuthController(Context context, Injector injector) {
+            super(context, injector);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 5a2b5e3..3472573 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -56,8 +56,10 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.colorextraction.ColorExtractor;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -141,6 +143,10 @@
     private BubbleController.BubbleExpandListener mBubbleExpandListener;
     @Mock
     private PendingIntent mDeleteIntent;
+    @Mock
+    private SysuiColorExtractor mColorExtractor;
+    @Mock
+    ColorExtractor.GradientColors mGradientColors;
 
     private BubbleData mBubbleData;
 
@@ -150,15 +156,16 @@
         mStatusBarView = new FrameLayout(mContext);
         mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
         mContext.addMockSystemService(FaceManager.class, mFaceManager);
+        when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
         // Bubbles get added to status bar window view
         mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
                 mActivityManager, mDozeParameters, mStatusBarStateController,
-                mConfigurationController, mKeyguardBypassController);
+                mConfigurationController, mKeyguardBypassController, mColorExtractor);
         mStatusBarWindowController.add(mStatusBarView, 120 /* height */);
 
         // Need notifications for bubbles
-        mNotificationTestHelper = new NotificationTestHelper(mContext);
+        mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
         mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
         mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
         mNonBubbleNotifRow = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 392a7cb..96ee079 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -99,7 +99,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mNotificationTestHelper = new NotificationTestHelper(mContext);
+        mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
         MockitoAnnotations.initMocks(this);
 
         mEntryA1 = createBubbleEntry(1, "a1", "package.a");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
index d011e48..3ba5d1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.classifier.brightline;
 
+import static com.android.systemui.classifier.Classifier.UNLOCK;
+
 import android.util.DisplayMetrics;
 import android.view.MotionEvent;
 
@@ -42,6 +44,7 @@
         displayMetrics.widthPixels = 1000;
         displayMetrics.heightPixels = 1000;
         mDataProvider = new FalsingDataProvider(displayMetrics);
+        mDataProvider.setInteractionType(UNLOCK);
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/PointerCountClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/PointerCountClassifierTest.java
index 341b74b..96b2028 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/PointerCountClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/PointerCountClassifierTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.classifier.brightline;
 
+import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
@@ -74,4 +76,21 @@
         motionEvent.recycle();
         assertThat(mClassifier.isFalseTouch(), is(true));
     }
+
+    @Test
+    public void testPass_multiPointerDragDown() {
+        MotionEvent.PointerProperties[] pointerProperties =
+                MotionEvent.PointerProperties.createArray(2);
+        pointerProperties[0].id = 0;
+        pointerProperties[1].id = 1;
+        MotionEvent.PointerCoords[] pointerCoords = MotionEvent.PointerCoords.createArray(2);
+        MotionEvent motionEvent = MotionEvent.obtain(
+                1, 1, MotionEvent.ACTION_DOWN, 2, pointerProperties, pointerCoords, 0, 0, 0, 0, 0,
+                0,
+                0, 0);
+        mClassifier.onTouchEvent(motionEvent);
+        motionEvent.recycle();
+        getDataProvider().setInteractionType(QUICK_SETTINGS);
+        assertThat(mClassifier.isFalseTouch(), is(false));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index 1ce0172..98ec4594 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -20,13 +20,11 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.Instrumentation;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Handler;
 import android.os.Looper;
@@ -34,7 +32,6 @@
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -45,23 +42,24 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 public class DozeDockHandlerTest extends SysuiTestCase {
-    private DozeDockHandler mDockHandler;
+    @Mock
     private DozeMachine mMachine;
-    private DozeHostFake mHost;
+    @Mock
+    private DozeHost mHost;
     private AmbientDisplayConfiguration mConfig;
-    private Instrumentation mInstrumentation;
     private DockManagerFake mDockManagerFake;
+    private DozeDockHandler mDockHandler;
 
     @Before
     public void setUp() throws Exception {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mMachine = mock(DozeMachine.class);
-        mHost = spy(new DozeHostFake());
+        MockitoAnnotations.initMocks(this);
         mConfig = DozeConfigurationUtil.createMockConfig();
         doReturn(false).when(mConfig).alwaysOnEnabled(anyInt());
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
deleted file mode 100644
index abfa755..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2017 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.doze;
-
-import android.annotation.NonNull;
-
-/**
- * A rudimentary fake for DozeHost.
- */
-class DozeHostFake implements DozeHost {
-    Callback callback;
-    boolean pulseExtended;
-    boolean animateWakeup;
-    boolean animateScreenOff;
-    boolean dozing;
-    float doubleTapX;
-    float doubleTapY;
-    float aodDimmingScrimOpacity;
-
-    @Override
-    public void addCallback(@NonNull Callback callback) {
-        this.callback = callback;
-    }
-
-    @Override
-    public void removeCallback(@NonNull Callback callback) {
-        this.callback = null;
-    }
-
-    @Override
-    public void startDozing() {
-        dozing = true;
-    }
-
-    @Override
-    public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
-        throw new RuntimeException("not implemented");
-    }
-
-    @Override
-    public void stopDozing() {
-        dozing = false;
-    }
-
-    @Override
-    public void dozeTimeTick() {
-        // Nothing to do in here. Real host would just update the UI.
-    }
-
-    @Override
-    public boolean isPowerSaveActive() {
-        return false;
-    }
-
-    @Override
-    public boolean isPulsingBlocked() {
-        return false;
-    }
-
-    @Override
-    public boolean isProvisioned() {
-        return false;
-    }
-
-    @Override
-    public boolean isBlockingDoze() {
-        return false;
-    }
-
-    @Override
-    public void onIgnoreTouchWhilePulsing(boolean ignore) {
-    }
-
-    @Override
-    public void extendPulse(int reason) {
-        pulseExtended = true;
-    }
-
-    @Override
-    public void stopPulsing() {}
-
-    @Override
-    public void setAnimateWakeup(boolean animateWakeup) {
-        this.animateWakeup = animateWakeup;
-    }
-
-    @Override
-    public void setAnimateScreenOff(boolean animateScreenOff) {
-        this.animateScreenOff = animateScreenOff;
-    }
-
-    @Override
-    public void onSlpiTap(float x, float y) {
-        doubleTapX = y;
-        doubleTapY = y;
-    }
-
-    @Override
-    public void setDozeScreenBrightness(int value) {
-    }
-
-    @Override
-    public void setAodDimmingScrim(float scrimOpacity) {
-        aodDimmingScrimOpacity = scrimOpacity;
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index aa62e9a..316b891 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -30,6 +30,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
 
 import android.content.Intent;
 import android.os.PowerManager;
@@ -45,6 +50,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -55,22 +62,27 @@
     static final int[] SENSOR_TO_OPACITY = new int[]{-1, 10, 0, 0, 0};
 
     DozeServiceFake mServiceFake;
-    DozeScreenBrightness mScreen;
     FakeSensorManager.FakeGenericSensor mSensor;
     FakeSensorManager mSensorManager;
-    DozeHostFake mHostFake;
+    @Mock
+    DozeHost mDozeHost;
+    DozeScreenBrightness mScreen;
 
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         Settings.System.putIntForUser(mContext.getContentResolver(),
                 Settings.System.SCREEN_BRIGHTNESS, DEFAULT_BRIGHTNESS,
                 UserHandle.USER_CURRENT);
+        doAnswer(invocation -> {
+            ((Runnable) invocation.getArgument(0)).run();
+            return null;
+        }).when(mDozeHost).prepareForGentleSleep(any());
         mServiceFake = new DozeServiceFake();
-        mHostFake = new DozeHostFake();
         mSensorManager = new FakeSensorManager(mContext);
         mSensor = mSensorManager.getFakeLightSensor();
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
-                mSensor.getSensor(), mHostFake, null /* handler */,
+                mSensor.getSensor(), mDozeHost, null /* handler */,
                 DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY,
                 true /* debuggable */);
     }
@@ -173,7 +185,7 @@
     @Test
     public void testNullSensor() throws Exception {
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
-                null /* sensor */, mHostFake, null /* handler */,
+                null /* sensor */, mDozeHost, null /* handler */,
                 DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY,
                 true /* debuggable */);
 
@@ -203,26 +215,7 @@
         mSensor.sendSensorEvent(0);
 
         assertEquals(1, mServiceFake.screenBrightness);
-        assertEquals(10/255f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
-    }
-
-    @Test
-    public void pausingAod_softBlanks() throws Exception {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-
-        mSensor.sendSensorEvent(2);
-
-        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
-        mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
-
-        assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
-
-        mSensor.sendSensorEvent(0);
-        assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
-
-        mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
-        assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+        verify(mDozeHost).setAodDimmingScrim(eq(10f / 255f));
     }
 
     @Test
@@ -232,8 +225,9 @@
         mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
         mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
 
+        reset(mDozeHost);
         mSensor.sendSensorEvent(1);
-        assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+        verify(mDozeHost).setAodDimmingScrim(eq(1f));
     }
 
     @Test
@@ -241,11 +235,12 @@
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
         mScreen.transitionTo(DOZE_AOD, DOZE);
-        assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+        verify(mDozeHost).setAodDimmingScrim(eq(1f));
 
+        reset(mDozeHost);
         mScreen.transitionTo(DOZE, DOZE_AOD);
         mSensor.sendSensorEvent(2);
-        assertEquals(0f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+        verify(mDozeHost).setAodDimmingScrim(eq(0f));
     }
 
     @Test
@@ -260,11 +255,10 @@
 
         mSensor.sendSensorEvent(0);
 
+        reset(mDozeHost);
         mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
-
         mSensor.sendSensorEvent(2);
-
-        assertEquals(0f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+        verify(mDozeHost).setAodDimmingScrim(eq(0f));
     }
 
     @Test
@@ -273,11 +267,11 @@
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
 
         mSensor.sendSensorEvent(2);
-
         mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
         mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
-        mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
 
-        assertEquals(0f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+        reset(mDozeHost);
+        mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
+        verify(mDozeHost).setAodDimmingScrim(eq(0f));
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index bfd448a..b92f173 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -18,6 +18,8 @@
 
 import static com.android.systemui.doze.DozeMachine.State.DOZE;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE;
 import static com.android.systemui.doze.DozeMachine.State.FINISH;
@@ -30,6 +32,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.os.Looper;
@@ -46,6 +51,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -53,12 +59,14 @@
 @SmallTest
 public class DozeScreenStateTest extends SysuiTestCase {
 
-    DozeServiceFake mServiceFake;
-    DozeScreenState mScreen;
-    FakeHandler mHandlerFake;
+    private DozeServiceFake mServiceFake;
+    private FakeHandler mHandlerFake;
     @Mock
-    DozeParameters mDozeParameters;
-    WakeLockFake mWakeLock;
+    private DozeHost mDozeHost;
+    @Mock
+    private DozeParameters mDozeParameters;
+    private WakeLockFake mWakeLock;
+    private DozeScreenState mScreen;
 
     @Before
     public void setUp() throws Exception {
@@ -68,7 +76,8 @@
         mServiceFake = new DozeServiceFake();
         mHandlerFake = new FakeHandler(Looper.getMainLooper());
         mWakeLock = new WakeLockFake();
-        mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeParameters, mWakeLock);
+        mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters,
+                mWakeLock);
     }
 
     @Test
@@ -183,4 +192,20 @@
         assertThat(mWakeLock.isHeld(), is(false));
     }
 
+    @Test
+    public void test_animatesPausing() {
+        ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+        doAnswer(invocation -> null).when(mDozeHost).prepareForGentleSleep(captor.capture());
+        mHandlerFake.setMode(QUEUEING);
+
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD_PAUSING);
+        mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
+
+        mHandlerFake.dispatchQueuedMessages();
+        verify(mDozeHost).prepareForGentleSleep(eq(captor.getValue()));
+        captor.getValue().run();
+        assertEquals(Display.STATE_OFF, mServiceFake.screenState);
+    }
+
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index e5ae6d5..777fec7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -50,14 +51,22 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
 public class DozeTriggersTest extends SysuiTestCase {
-    private DozeTriggers mTriggers;
+
+    @Mock
     private DozeMachine mMachine;
-    private DozeHostFake mHost;
+    @Mock
+    private DozeHost mHost;
+    @Mock
+    private AlarmManager mAlarmManager;
+    private DozeTriggers mTriggers;
     private FakeSensorManager mSensors;
     private Sensor mTapSensor;
     private DockManager mDockManagerFake;
@@ -65,9 +74,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mMachine = mock(DozeMachine.class);
-        AlarmManager alarmManager = mock(AlarmManager.class);
-        mHost = spy(new DozeHostFake());
+        MockitoAnnotations.initMocks(this);
         AmbientDisplayConfiguration config = DozeConfigurationUtil.createMockConfig();
         DozeParameters parameters = DozeConfigurationUtil.createMockParameters();
         mSensors = spy(new FakeSensorManager(mContext));
@@ -78,7 +85,7 @@
                 new AsyncSensorManager(mSensors, null, new Handler());
         mProximitySensor = new FakeProximitySensor(getContext(), asyncSensorManager);
 
-        mTriggers = new DozeTriggers(mContext, mMachine, mHost, alarmManager, config, parameters,
+        mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, config, parameters,
                 asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true,
                 mDockManagerFake, mProximitySensor, mock(DozeLog.class));
         waitForSensorManager();
@@ -87,19 +94,21 @@
     @Test
     public void testOnNotification_stillWorksAfterOneFailedProxCheck() throws Exception {
         when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+        ArgumentCaptor<DozeHost.Callback> captor = ArgumentCaptor.forClass(DozeHost.Callback.class);
+        doAnswer(invocation -> null).when(mHost).addCallback(captor.capture());
 
         mTriggers.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
         mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
         clearInvocations(mMachine);
 
         mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(true, 1));
-        mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
+        captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
         mProximitySensor.alertListeners();
 
         verify(mMachine, never()).requestState(any());
         verify(mMachine, never()).requestPulse(anyInt());
 
-        mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
+        captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
         waitForSensorManager();
         mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(false, 2));
         mProximitySensor.alertListeners();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 4958c64..8f4de3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -682,8 +682,7 @@
     }
 
     private void createPowerUi() {
-        mPowerUI = new PowerUI(mBroadcastDispatcher);
-        mPowerUI.mContext = mContext;
+        mPowerUI = new PowerUI(mContext, mBroadcastDispatcher);
         mPowerUI.mComponents = mContext.getComponents();
         mPowerUI.mThermalService = mThermalServiceMock;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
new file mode 100644
index 0000000..4becd52
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.qs.external
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.service.quicksettings.IQSTileService
+import android.service.quicksettings.Tile
+import android.test.suitebuilder.annotation.SmallTest
+import android.view.IWindowManager
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.QSTileHost
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomTileTest : SysuiTestCase() {
+
+    companion object {
+        const val packageName = "test_package"
+        const val className = "test_class"
+        val componentName = ComponentName(packageName, className)
+        val TILE_SPEC = CustomTile.toSpec(componentName)
+    }
+
+    @Mock private lateinit var mTileHost: QSTileHost
+    @Mock private lateinit var mTileService: IQSTileService
+    @Mock private lateinit var mTileServices: TileServices
+    @Mock private lateinit var mTileServiceManager: TileServiceManager
+    @Mock private lateinit var mWindowService: IWindowManager
+    @Mock private lateinit var mPackageManager: PackageManager
+    @Mock private lateinit var mApplicationInfo: ApplicationInfo
+    @Mock private lateinit var mServiceInfo: ServiceInfo
+
+    private lateinit var customTile: CustomTile
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        mContext.addMockSystemService("window", mWindowService)
+        mContext.setMockPackageManager(mPackageManager)
+        `when`(mTileHost.tileServices).thenReturn(mTileServices)
+        `when`(mTileHost.context).thenReturn(mContext)
+        `when`(mTileServices.getTileWrapper(any(CustomTile::class.java)))
+                .thenReturn(mTileServiceManager)
+        `when`(mTileServiceManager.tileService).thenReturn(mTileService)
+        `when`(mPackageManager.getApplicationInfo(anyString(), anyInt()))
+                .thenReturn(mApplicationInfo)
+
+        `when`(mPackageManager.getServiceInfo(any(ComponentName::class.java), anyInt()))
+                .thenReturn(mServiceInfo)
+        mServiceInfo.applicationInfo = mApplicationInfo
+
+        customTile = CustomTile.create(mTileHost, TILE_SPEC)
+    }
+
+    @Test
+    fun testBooleanTileHasBooleanState() {
+        `when`(mTileServiceManager.isBooleanTile).thenReturn(true)
+        customTile = CustomTile.create(mTileHost, TILE_SPEC)
+
+        assertTrue(customTile.state is QSTile.BooleanState)
+        assertTrue(customTile.newTileState() is QSTile.BooleanState)
+    }
+
+    @Test
+    fun testRegularTileHasNotBooleanState() {
+        assertFalse(customTile.state is QSTile.BooleanState)
+        assertFalse(customTile.newTileState() is QSTile.BooleanState)
+    }
+
+    @Test
+    fun testValueUpdatedInBooleanTile() {
+        `when`(mTileServiceManager.isBooleanTile).thenReturn(true)
+        customTile = CustomTile.create(mTileHost, TILE_SPEC)
+        customTile.qsTile.icon = mock(Icon::class.java)
+        `when`(customTile.qsTile.icon.loadDrawable(any(Context::class.java)))
+                .thenReturn(mock(Drawable::class.java))
+
+        val state = customTile.newTileState()
+        assertTrue(state is QSTile.BooleanState)
+
+        customTile.qsTile.state = Tile.STATE_INACTIVE
+        customTile.handleUpdateState(state, null)
+        assertFalse((state as QSTile.BooleanState).value)
+
+        customTile.qsTile.state = Tile.STATE_ACTIVE
+        customTile.handleUpdateState(state, null)
+        assertTrue(state.value)
+
+        customTile.qsTile.state = Tile.STATE_UNAVAILABLE
+        customTile.handleUpdateState(state, null)
+        assertFalse(state.value)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index f35295c..11b0c69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -101,6 +101,7 @@
             defaultServiceInfo = new ServiceInfo();
             defaultServiceInfo.metaData = new Bundle();
             defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_ACTIVE_TILE, true);
+            defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_BOOLEAN_TILE, true);
         }
         when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), anyInt()))
                 .thenReturn(defaultServiceInfo);
@@ -237,4 +238,9 @@
         verifyBind(2);
         verify(mMockTileService, times(2)).onStartListening();
     }
+
+    @Test
+    public void testBooleanTile() throws Exception {
+        assertTrue(mStateManager.isBooleanTile());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index a0a410d..e67aa69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -52,7 +52,7 @@
  */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class NonPhoneDependencyTest extends SysuiTestCase {
     @Mock private NotificationPresenter mPresenter;
     @Mock private NotificationListContainer mListContainer;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 0569c55..85a0fbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -52,6 +52,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import com.google.android.collect.Lists;
 
@@ -83,6 +84,7 @@
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
         mDependency.injectTestDependency(DeviceProvisionedController.class,
                 mDeviceProvisionedController);
+        mDependency.injectMockDependency(KeyguardStateController.class);
 
         mContext.addMockSystemService(UserManager.class, mUserManager);
         mCurrentUserId = ActivityManager.getCurrentUser();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index de77af8..cb4096c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -42,6 +42,8 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.systemui.TestableDependency;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.bubbles.BubblesTestActivity;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -51,6 +53,7 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.tests.R;
 
 /**
@@ -75,8 +78,11 @@
     private ExpandableNotificationRow mRow;
     private HeadsUpManagerPhone mHeadsUpManager;
 
-    public NotificationTestHelper(Context context) {
+    public NotificationTestHelper(Context context, TestableDependency dependency) {
         mContext = context;
+        dependency.injectMockDependency(NotificationMediaManager.class);
+        dependency.injectMockDependency(BubbleController.class);
+        dependency.injectMockDependency(StatusBarWindowController.class);
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         StatusBarStateController stateController = mock(StatusBarStateController.class);
         mGroupManager = new NotificationGroupManager(stateController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 9e72504..6efa57d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -103,7 +103,7 @@
         mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
         mDependency.injectTestDependency(ShadeController.class, mShadeController);
 
-        mHelper = new NotificationTestHelper(mContext);
+        mHelper = new NotificationTestHelper(mContext, mDependency);
 
         when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
index db2c878..4103ede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
@@ -47,7 +47,7 @@
     @Before
     public void setUp() throws Exception {
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
-        mNotificationTestHelper = new NotificationTestHelper(getContext());
+        mNotificationTestHelper = new NotificationTestHelper(getContext(), mDependency);
         mHostLayout = new FrameLayout(getContext());
         mObserver = new AboveShelfObserver(mHostLayout);
         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index edd0a10..45e1721 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -42,6 +42,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -94,10 +95,11 @@
         mDependency.injectTestDependency(NotificationGroupManager.class,
                 new NotificationGroupManager(mock(StatusBarStateController.class)));
         mDependency.injectMockDependency(ShadeController.class);
+        mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
         mDependency.injectTestDependency(NotificationData.KeyguardEnvironment.class, mEnvironment);
         when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
         when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-        mRow = new NotificationTestHelper(getContext()).createRow();
+        mRow = new NotificationTestHelper(getContext(), mDependency).createRow();
         mNotificationFilter = new NotificationFilter();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
index 8207a04..170c661 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
@@ -42,6 +42,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
@@ -85,6 +86,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
 
         when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
index 59c76a8..1be6f61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
@@ -74,6 +74,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SbnBuilder;
@@ -137,13 +138,14 @@
         mDependency.injectTestDependency(NotificationGroupManager.class,
                 new NotificationGroupManager(mock(StatusBarStateController.class)));
         mDependency.injectMockDependency(ShadeController.class);
+        mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
         mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
         when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
         when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
         mNotificationData = new TestableNotificationData(
                 mock(NotificationSectionsFeatureManager.class));
         mNotificationData.updateRanking(mock(NotificationListenerService.RankingMap.class), "");
-        mRow = new NotificationTestHelper(getContext()).createRow();
+        mRow = new NotificationTestHelper(getContext(), mDependency).createRow();
         Dependency.get(InitController.class).executePostInitTasks();
     }
 
@@ -159,10 +161,11 @@
     @Test
     public void testAllRelevantNotisTaggedWithAppOps() throws Exception {
         mNotificationData.add(mRow.getEntry());
-        ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow();
+        ExpandableNotificationRow row2 = new NotificationTestHelper(getContext(), mDependency)
+                .createRow();
         mNotificationData.add(row2.getEntry());
         ExpandableNotificationRow diffPkg =
-                new NotificationTestHelper(getContext()).createRow("pkg", 4000,
+                new NotificationTestHelper(getContext(), mDependency).createRow("pkg", 4000,
                         Process.myUserHandle());
         mNotificationData.add(diffPkg.getEntry());
 
@@ -189,7 +192,8 @@
     @Test
     public void testAppOpsRemoval() throws Exception {
         mNotificationData.add(mRow.getEntry());
-        ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow();
+        ExpandableNotificationRow row2 = new NotificationTestHelper(getContext(), mDependency)
+                .createRow();
         mNotificationData.add(row2.getEntry());
 
         ArraySet<Integer> expectedOps = new ArraySet<>();
@@ -221,7 +225,8 @@
     public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications()
             throws Exception {
         mNotificationData.add(mRow.getEntry());
-        ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow();
+        ExpandableNotificationRow row2 = new NotificationTestHelper(getContext(), mDependency)
+                .createRow();
         mNotificationData.add(row2.getEntry());
 
         when(mEnvironment.isNotificationForCurrentProfiles(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index c56a168..5e6c963 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -82,7 +82,7 @@
     @Before
     public void setUp() throws Exception {
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
-        mNotificationTestHelper = new NotificationTestHelper(mContext);
+        mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
         mGroupRow = mNotificationTestHelper.createGroup();
         mGroupRow.setHeadsUpAnimatingAwayListener(
                 animatingAway -> mHeadsUpAnimatingAway = animatingAway);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index cc89504..444a6e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -88,7 +88,7 @@
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
         mDependency.injectMockDependency(BubbleController.class);
 
-        mHelper = new NotificationTestHelper(mContext);
+        mHelper = new NotificationTestHelper(mContext, mDependency);
 
         mBlockingHelperManager = new NotificationBlockingHelperManager(mContext);
         // By default, have the shade visible/expanded.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index ccadcc3..71c2e11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -77,7 +77,7 @@
                 .setContentTitle("Title")
                 .setContentText("Text")
                 .setStyle(new Notification.BigTextStyle().bigText("big text"));
-        ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow(
+        ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
                 mBuilder.build());
         mRow = spy(row);
         mNotificationInflater = new NotificationContentInflater(mRow);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index db6b613..41fe173 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -62,6 +62,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -118,9 +119,10 @@
                 mDeviceProvisionedController);
         mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
         mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+        mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
         mHandler = Handler.createAsync(mTestableLooper.getLooper());
         mContext.putComponent(StatusBar.class, mStatusBar);
-        mHelper = new NotificationTestHelper(mContext);
+        mHelper = new NotificationTestHelper(mContext, mDependency);
 
         mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager);
         mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index 49a6410..d280f18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -44,7 +44,7 @@
     @Before
     public void setUp() throws Exception {
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
-        mRow = new NotificationTestHelper(mContext).createRow();
+        mRow = new NotificationTestHelper(mContext, mDependency).createRow();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
index 4f844f0..4f45f68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
@@ -94,7 +94,7 @@
         mNotif = builder.build();
         assertTrue(mNotif.hasMediaSession());
 
-        mRow = new NotificationTestHelper(mContext).createRow(mNotif);
+        mRow = new NotificationTestHelper(mContext, mDependency).createRow(mNotif);
 
         RemoteViews views = new RemoteViews(mContext.getPackageName(),
                 com.android.internal.R.layout.notification_template_material_big_media);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
index 5463159..14e2fde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
@@ -50,7 +50,7 @@
     public void setup() throws Exception {
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
         mView = mock(View.class);
-        mRow = new NotificationTestHelper(getContext()).createRow();
+        mRow = new NotificationTestHelper(getContext(), mDependency).createRow();
         mNotificationViewWrapper = new TestableNotificationViewWrapper(mContext, mView, mRow);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 22d2585..ddd2884e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -45,7 +45,7 @@
     @Before
     public void setUp() throws Exception {
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
-        mNotificationTestHelper = new NotificationTestHelper(mContext);
+        mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
         mGroup = mNotificationTestHelper.createGroup();
         mChildrenContainer = mGroup.getChildrenContainer();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 3f467ea..34a309f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -70,7 +70,7 @@
                 mBypassController,
                 new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext));
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
-        NotificationTestHelper testHelper = new NotificationTestHelper(getContext());
+        NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency);
         mFirst = testHelper.createRow();
         mFirst.setHeadsUpAnimatingAwayListener(animatingAway
                 -> mRoundnessManager.onHeadsupAnimatingAwayChanged(mFirst, animatingAway));
@@ -150,7 +150,8 @@
                 createSection(mFirst, mSecond),
                 createSection(null, null)
         });
-        ExpandableNotificationRow row = new NotificationTestHelper(getContext()).createRow();
+        ExpandableNotificationRow row = new NotificationTestHelper(getContext(), mDependency)
+                .createRow();
         NotificationEntry entry = mock(NotificationEntry.class);
         when(entry.getRow()).thenReturn(row);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 11ae0cc..4b82f59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -56,6 +56,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShelf;
@@ -70,6 +71,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -167,6 +169,8 @@
                 mHeadsUpManager,
                 mKeyguardBypassController,
                 new FalsingManagerFake(),
+                mock(NotificationLockscreenUserManager.class),
+                mock(NotificationGutsManager.class),
                 new NotificationSectionsFeatureManager(new DeviceConfigProxyFake(), mContext));
         mStackScroller = spy(mStackScrollerInternal);
         mStackScroller.setShelf(notificationShelf);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoHideControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoHideControllerTest.java
index f614354..16f02d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoHideControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoHideControllerTest.java
@@ -31,6 +31,7 @@
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
+import android.view.IWindowManager;
 import android.view.View;
 
 import androidx.test.filters.SmallTest;
@@ -38,6 +39,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -58,7 +60,8 @@
     public void setUp() {
         mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
         mAutoHideController =
-                spy(new AutoHideController(mContext, Dependency.get(Dependency.MAIN_HANDLER)));
+                spy(new AutoHideController(mContext, Dependency.get(Dependency.MAIN_HANDLER),
+                        mock(NotificationRemoteInputManager.class), mock(IWindowManager.class)));
         mAutoHideController.mDisplayId = DEFAULT_DISPLAY;
         mAutoHideController.mSystemUiVisibility = View.VISIBLE;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index ff9aae7..72bea56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -76,6 +76,8 @@
     private Handler mHandler;
     @Mock
     private KeyguardBypassController mKeyguardBypassController;
+    @Mock
+    private DozeParameters mDozeParameters;
     private BiometricUnlockController mBiometricUnlockController;
 
     @Before
@@ -92,7 +94,8 @@
                 mStatusBarWindowController);
         mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
                 mKeyguardViewMediator, mScrimController, mStatusBar, mKeyguardStateController,
-                mHandler, mUpdateMonitor, 0 /* wakeUpDelay */, mKeyguardBypassController);
+                mHandler, mUpdateMonitor, 0 /* wakeUpDelay */, mKeyguardBypassController,
+                mDozeParameters);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java
new file mode 100644
index 0000000..df1233a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java
@@ -0,0 +1,369 @@
+/*
+ * 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.phone;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DoubleTapHelperTest extends SysuiTestCase {
+
+    private DoubleTapHelper mDoubleTapHelper;
+    private int mTouchSlop;
+    private int mDoubleTouchSlop;
+    @Mock private View mView;
+    @Mock private DoubleTapHelper.ActivationListener mActivationListener;
+    @Mock private DoubleTapHelper.DoubleTapListener mDoubleTapListener;
+    @Mock private DoubleTapHelper.SlideBackListener mSlideBackListener;
+    @Mock private DoubleTapHelper.DoubleTapLogListener mDoubleTapLogListener;
+    @Mock private Resources mResources;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+        // The double tap slop has to be less than the regular slop, otherwise it has no effect.
+        mDoubleTouchSlop = mTouchSlop - 1;
+        when(mView.getContext()).thenReturn(mContext);
+        when(mView.getResources()).thenReturn(mResources);
+        when(mResources.getDimension(R.dimen.double_tap_slop))
+                .thenReturn((float) mDoubleTouchSlop);
+
+        mDoubleTapHelper = new DoubleTapHelper(mView,
+                                               mActivationListener,
+                                               mDoubleTapListener,
+                                               mSlideBackListener, mDoubleTapLogListener);
+    }
+
+    @Test
+    public void testDoubleTap_success() {
+        long downtimeA = SystemClock.uptimeMillis();
+        long downtimeB = downtimeA + 100;
+
+        MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+                                                 downtimeA,
+                                                 MotionEvent.ACTION_DOWN,
+                                                 1,
+                                                 1,
+                                                 0);
+        MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+                                               downtimeA + 1,
+                                               MotionEvent.ACTION_UP,
+                                               1,
+                                               1,
+                                               0);
+        MotionEvent evDownB = MotionEvent.obtain(downtimeB,
+                                                 downtimeB,
+                                                 MotionEvent.ACTION_DOWN,
+                                                 1,
+                                                 1,
+                                                 0);
+        MotionEvent evUpB = MotionEvent.obtain(downtimeB,
+                                               downtimeB + 1,
+                                               MotionEvent.ACTION_UP,
+                                               1,
+                                               1,
+                                               0);
+
+        mDoubleTapHelper.onTouchEvent(evDownA);
+        mDoubleTapHelper.onTouchEvent(evUpA);
+        verify(mActivationListener).onActiveChanged(true);
+        verify(mView).postDelayed(any(Runnable.class), anyLong());
+        verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
+        verify(mDoubleTapListener, never()).onDoubleTap();
+
+        mDoubleTapHelper.onTouchEvent(evDownB);
+        mDoubleTapHelper.onTouchEvent(evUpB);
+        verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+        verify(mDoubleTapListener).onDoubleTap();
+
+        evDownA.recycle();
+        evUpA.recycle();
+        evDownB.recycle();
+        evUpB.recycle();
+    }
+
+    @Test
+    public void testSingleTap_timeout() {
+        long downtimeA = SystemClock.uptimeMillis();
+
+        MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+                                                 downtimeA,
+                                                 MotionEvent.ACTION_DOWN,
+                                                 1,
+                                                 1,
+                                                 0);
+        MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+                                               downtimeA + 1,
+                                               MotionEvent.ACTION_UP,
+                                               1,
+                                               1,
+                                               0);
+
+        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        mDoubleTapHelper.onTouchEvent(evDownA);
+        mDoubleTapHelper.onTouchEvent(evUpA);
+        verify(mActivationListener).onActiveChanged(true);
+        verify(mView).postDelayed(runnableCaptor.capture(), anyLong());
+        runnableCaptor.getValue().run();
+        verify(mActivationListener).onActiveChanged(true);
+
+        evDownA.recycle();
+        evUpA.recycle();
+    }
+
+    @Test
+    public void testSingleTap_slop() {
+        long downtimeA = SystemClock.uptimeMillis();
+
+        MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+                                                 downtimeA,
+                                                 MotionEvent.ACTION_DOWN,
+                                                 1,
+                                                 1,
+                                                 0);
+        MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+                                               downtimeA + 1,
+                                               MotionEvent.ACTION_UP,
+                                               1 + mTouchSlop,
+                                               1,
+                                               0);
+
+        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        mDoubleTapHelper.onTouchEvent(evDownA);
+        mDoubleTapHelper.onTouchEvent(evUpA);
+        verify(mActivationListener, never()).onActiveChanged(true);
+        verify(mDoubleTapListener, never()).onDoubleTap();
+
+        evDownA.recycle();
+        evUpA.recycle();
+    }
+
+    @Test
+    public void testDoubleTap_slop() {
+        long downtimeA = SystemClock.uptimeMillis();
+        long downtimeB = downtimeA + 100;
+
+        MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+                                                 downtimeA,
+                                                 MotionEvent.ACTION_DOWN,
+                                                 1,
+                                                 1,
+                                                 0);
+        MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+                                               downtimeA + 1,
+                                               MotionEvent.ACTION_UP,
+                                               1,
+                                               1,
+                                               0);
+        MotionEvent evDownB = MotionEvent.obtain(downtimeB,
+                                                 downtimeB,
+                                                 MotionEvent.ACTION_DOWN,
+                                                 1,
+                                                 1,
+                                                 0);
+        MotionEvent evUpB = MotionEvent.obtain(downtimeB,
+                                               downtimeB + 1,
+                                               MotionEvent.ACTION_UP,
+                                               1,
+                                               1 + mDoubleTouchSlop,
+                                               0);
+
+        mDoubleTapHelper.onTouchEvent(evDownA);
+        mDoubleTapHelper.onTouchEvent(evUpA);
+        verify(mActivationListener).onActiveChanged(true);
+        verify(mView).postDelayed(any(Runnable.class), anyLong());
+
+        mDoubleTapHelper.onTouchEvent(evDownB);
+        mDoubleTapHelper.onTouchEvent(evUpB);
+        verify(mDoubleTapLogListener).onDoubleTapLog(false, 0, mDoubleTouchSlop);
+        verify(mActivationListener).onActiveChanged(false);
+        verify(mDoubleTapListener, never()).onDoubleTap();
+
+        evDownA.recycle();
+        evUpA.recycle();
+        evDownB.recycle();
+        evUpB.recycle();
+    }
+
+    @Test
+    public void testSlideBack() {
+        long downtimeA = SystemClock.uptimeMillis();
+        long downtimeB = downtimeA + 100;
+
+        MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+                                                 downtimeA,
+                                                 MotionEvent.ACTION_DOWN,
+                                                 1,
+                                                 1,
+                                                 0);
+        MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+                                               downtimeA + 1,
+                                               MotionEvent.ACTION_UP,
+                                               1,
+                                               1,
+                                               0);
+        MotionEvent evDownB = MotionEvent.obtain(downtimeB,
+                                                 downtimeB,
+                                                 MotionEvent.ACTION_DOWN,
+                                                 1,
+                                                 1,
+                                                 0);
+        MotionEvent evUpB = MotionEvent.obtain(downtimeB,
+                                               downtimeB + 1,
+                                               MotionEvent.ACTION_UP,
+                                               1,
+                                               1,
+                                               0);
+
+        when(mSlideBackListener.onSlideBack()).thenReturn(true);
+
+        mDoubleTapHelper.onTouchEvent(evDownA);
+        mDoubleTapHelper.onTouchEvent(evUpA);
+        verify(mActivationListener, never()).onActiveChanged(true);
+        verify(mActivationListener, never()).onActiveChanged(false);
+        verify(mDoubleTapListener, never()).onDoubleTap();
+        mDoubleTapHelper.onTouchEvent(evDownB);
+        mDoubleTapHelper.onTouchEvent(evUpB);
+        verify(mActivationListener, never()).onActiveChanged(true);
+        verify(mActivationListener, never()).onActiveChanged(false);
+        verify(mDoubleTapListener, never()).onDoubleTap();
+
+        evDownA.recycle();
+        evUpA.recycle();
+        evDownB.recycle();
+        evUpB.recycle();
+    }
+
+
+    @Test
+    public void testMoreThanTwoTaps() {
+        long downtimeA = SystemClock.uptimeMillis();
+        long downtimeB = downtimeA + 100;
+        long downtimeC = downtimeB + 100;
+        long downtimeD = downtimeC + 100;
+
+        MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+                                                 downtimeA,
+                                                 MotionEvent.ACTION_DOWN,
+                                                 1,
+                                                 1,
+                                                 0);
+        MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+                                               downtimeA + 1,
+                                               MotionEvent.ACTION_UP,
+                                               1,
+                                               1,
+                                               0);
+        MotionEvent evDownB = MotionEvent.obtain(downtimeB,
+                                                 downtimeB,
+                                                 MotionEvent.ACTION_DOWN,
+                                                 1,
+                                                 1,
+                                                 0);
+        MotionEvent evUpB = MotionEvent.obtain(downtimeB,
+                                               downtimeB + 1,
+                                               MotionEvent.ACTION_UP,
+                                               1,
+                                               1,
+                                               0);
+        MotionEvent evDownC = MotionEvent.obtain(downtimeC,
+                                                 downtimeC,
+                                                 MotionEvent.ACTION_DOWN,
+                                                 1,
+                                                 1,
+                                                 0);
+        MotionEvent evUpC = MotionEvent.obtain(downtimeC,
+                                               downtimeC + 1,
+                                               MotionEvent.ACTION_UP,
+                                               1,
+                                               1,
+                                               0);
+        MotionEvent evDownD = MotionEvent.obtain(downtimeD,
+                                                 downtimeD,
+                                                 MotionEvent.ACTION_DOWN,
+                                                 1,
+                                                 1,
+                                                 0);
+        MotionEvent evUpD = MotionEvent.obtain(downtimeD,
+                                               downtimeD + 1,
+                                               MotionEvent.ACTION_UP,
+                                               1,
+                                               1,
+                                               0);
+
+        mDoubleTapHelper.onTouchEvent(evDownA);
+        mDoubleTapHelper.onTouchEvent(evUpA);
+        verify(mActivationListener).onActiveChanged(true);
+        verify(mView).postDelayed(any(Runnable.class), anyLong());
+        verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
+        verify(mDoubleTapListener, never()).onDoubleTap();
+
+        mDoubleTapHelper.onTouchEvent(evDownB);
+        mDoubleTapHelper.onTouchEvent(evUpB);
+        verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+        verify(mDoubleTapListener).onDoubleTap();
+
+        reset(mView);
+        reset(mActivationListener);
+        reset(mDoubleTapLogListener);
+        reset(mDoubleTapListener);
+
+        mDoubleTapHelper.onTouchEvent(evDownC);
+        mDoubleTapHelper.onTouchEvent(evUpC);
+        verify(mActivationListener).onActiveChanged(true);
+        verify(mView).postDelayed(any(Runnable.class), anyLong());
+        verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
+        verify(mDoubleTapListener, never()).onDoubleTap();
+
+        mDoubleTapHelper.onTouchEvent(evDownD);
+        mDoubleTapHelper.onTouchEvent(evUpD);
+        verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+        verify(mDoubleTapListener).onDoubleTap();
+
+        evDownA.recycle();
+        evUpA.recycle();
+        evDownB.recycle();
+        evUpB.recycle();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 60050b1..debc840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -17,68 +17,73 @@
 package com.android.systemui.statusbar.phone;
 
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 
-import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.PowerManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
+import com.android.systemui.tuner.TunerService;
 
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class DozeParametersTest extends SysuiTestCase {
 
+    private DozeParameters mDozeParameters;
+
+    @Mock Resources mResources;
+    @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+    @Mock private AlwaysOnDisplayPolicy mAlwaysOnDisplayPolicy;
+    @Mock private PowerManager mPowerManager;
+    @Mock private TunerService mTunerService;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mDozeParameters = new DozeParameters(
+            mResources,
+            mAmbientDisplayConfiguration,
+            mAlwaysOnDisplayPolicy,
+            mPowerManager,
+            mTunerService
+        );
+    }
     @Test
     public void test_setControlScreenOffAnimation_setsDozeAfterScreenOff_false() {
-        TestableDozeParameters dozeParameters = new TestableDozeParameters(getContext());
-        PowerManager mockedPowerManager = dozeParameters.getPowerManager();
-        dozeParameters.setControlScreenOffAnimation(true);
-        reset(mockedPowerManager);
-        dozeParameters.setControlScreenOffAnimation(false);
-        verify(mockedPowerManager).setDozeAfterScreenOff(eq(true));
+        mDozeParameters.setControlScreenOffAnimation(true);
+        reset(mPowerManager);
+        mDozeParameters.setControlScreenOffAnimation(false);
+        verify(mPowerManager).setDozeAfterScreenOff(eq(true));
     }
 
     @Test
     public void test_setControlScreenOffAnimation_setsDozeAfterScreenOff_true() {
-        TestableDozeParameters dozeParameters = new TestableDozeParameters(getContext());
-        PowerManager mockedPowerManager = dozeParameters.getPowerManager();
-        dozeParameters.setControlScreenOffAnimation(false);
-        reset(mockedPowerManager);
-        dozeParameters.setControlScreenOffAnimation(true);
-        verify(dozeParameters.getPowerManager()).setDozeAfterScreenOff(eq(false));
+        mDozeParameters.setControlScreenOffAnimation(false);
+        reset(mPowerManager);
+        mDozeParameters.setControlScreenOffAnimation(true);
+        verify(mPowerManager).setDozeAfterScreenOff(eq(false));
     }
 
     @Test
     public void test_getWallpaperAodDuration_when_shouldControlScreenOff() {
-        TestableDozeParameters dozeParameters = new TestableDozeParameters(getContext());
-        dozeParameters.setControlScreenOffAnimation(true);
-        Assert.assertEquals("wallpaper hides faster when controlling screen off",
-                dozeParameters.getWallpaperAodDuration(),
+        mDozeParameters.setControlScreenOffAnimation(true);
+        Assert.assertEquals(
+                "wallpaper hides faster when controlling screen off",
+                mDozeParameters.getWallpaperAodDuration(),
                 DozeScreenState.ENTER_DOZE_HIDE_WALLPAPER_DELAY);
     }
-
-    private class TestableDozeParameters extends DozeParameters {
-        private PowerManager mPowerManager;
-
-        TestableDozeParameters(Context context) {
-            super(context);
-            mPowerManager = mock(PowerManager.class);
-        }
-
-        @Override
-        protected PowerManager getPowerManager() {
-            return mPowerManager;
-        }
-    }
-
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index a38094d..0216d2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -61,11 +62,12 @@
     private StatusBarStateController mStatusbarStateController;
     private KeyguardBypassController mBypassController;
     private NotificationWakeUpCoordinator mWakeUpCoordinator;
+    private KeyguardStateController mKeyguardStateController;
 
     @Before
     public void setUp() throws Exception {
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
-        NotificationTestHelper testHelper = new NotificationTestHelper(getContext());
+        NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency);
         mFirst = testHelper.createRow();
         mDependency.injectTestDependency(DarkIconDispatcher.class, mDarkIconDispatcher);
         mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class),
@@ -75,12 +77,14 @@
         mStatusbarStateController = mock(StatusBarStateController.class);
         mBypassController = mock(KeyguardBypassController.class);
         mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
+        mKeyguardStateController = mock(KeyguardStateController.class);
         mHeadsUpAppearanceController = new HeadsUpAppearanceController(
                 mock(NotificationIconAreaController.class),
                 mHeadsUpManager,
                 mStatusbarStateController,
                 mBypassController,
                 mWakeUpCoordinator,
+                mKeyguardStateController,
                 mHeadsUpStatusBarView,
                 mStackScroller,
                 mPanelView,
@@ -158,6 +162,7 @@
                 mStatusbarStateController,
                 mBypassController,
                 mWakeUpCoordinator,
+                mKeyguardStateController,
                 mHeadsUpStatusBarView,
                 mStackScroller,
                 mPanelView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index ef9665a..6fcf550 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -29,6 +29,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
@@ -36,6 +37,7 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -85,6 +87,9 @@
         when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
                 .thenReturn(TEST_AUTO_DISMISS_TIME);
         when(mVSManager.isReorderingAllowed()).thenReturn(true);
+        mDependency.injectMockDependency(BubbleController.class);
+        mDependency.injectMockDependency(StatusBarWindowController.class);
+        mDependency.injectMockDependency(ConfigurationController.class);
         mHeadsUpManager = new TestableHeadsUpManagerPhone(mContext, mStatusBarWindowView,
                 mGroupManager, mBar, mVSManager, mStatusBarStateController, mBypassController);
         super.setUp();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
index 688a6fb..2b091f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
@@ -1,14 +1,11 @@
 package com.android.systemui.statusbar.phone
 
-import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.LayoutInflater
-
+import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.KeyguardIndicationController
-
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -17,7 +14,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 class KeyguardBottomAreaTest : SysuiTestCase() {
 
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index cb87d7d..67b8e07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -100,6 +100,7 @@
         MockitoAnnotations.initMocks(this);
         mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
         mDependency.injectTestDependency(KeyguardSecurityModel.class, mKeyguardSecurityModel);
+        mDependency.injectMockDependency(KeyguardStateController.class);
         when(mKeyguardSecurityModel.getSecurityMode(anyInt()))
                 .thenReturn(KeyguardSecurityModel.SecurityMode.None);
         DejankUtils.setImmediate(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
index 93fdce1..b1580ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
@@ -28,8 +28,10 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.LightBarTransitionsController.DarkIntensityApplier;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -50,6 +52,8 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
+        mDependency.injectMockDependency(KeyguardStateController.class);
+        mDependency.injectMockDependency(StatusBarStateController.class);
         mLightBarTransitionsController = new LightBarTransitionsController(mContext, mApplier);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
index 81e8abf..098a69f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
@@ -35,7 +35,10 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.After;
 import org.junit.Before;
@@ -63,6 +66,9 @@
                 (SysuiTestableContext) mContext.createDisplayContext(display);
         context.putComponent(CommandQueue.class, mock(CommandQueue.class));
 
+        mDependency.injectMockDependency(AssistManager.class);
+        mDependency.injectMockDependency(OverviewProxyService.class);
+        mDependency.injectMockDependency(KeyguardStateController.class);
         mNavBar = new NavigationBarView(context, null);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
index be69f5f..6433376 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
@@ -33,6 +33,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 
 import org.junit.Before;
@@ -60,6 +61,8 @@
 
     @Before
     public void setup() {
+        mDependency.injectMockDependency(AssistManager.class);
+
         mGroup = new ContextualButtonGroup(GROUP_ID);
         mBtn0 = new ContextualButton(BUTTON_0_ID, ICON_RES_ID);
         mBtn1 = new ContextualButton(BUTTON_1_ID, ICON_RES_ID);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java
index cec7feb..991e495 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java
@@ -31,6 +31,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.CommandQueue;
 
 import org.junit.After;
@@ -51,6 +53,9 @@
     @Before
     public void setUp() {
         mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
+        mDependency.injectMockDependency(AssistManager.class);
+        mDependency.injectMockDependency(OverviewProxyService.class);
+        mDependency.injectMockDependency(NavigationModeController.class);
 
         mNavBarInflaterView = spy(new NavigationBarInflaterView(mContext, null));
         doNothing().when(mNavBarInflaterView).createInflaters();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
index bb109bd..1e9378a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
@@ -28,7 +28,11 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -44,6 +48,12 @@
     @Before
     public void setup() {
         mDependency.injectMockDependency(IWindowManager.class);
+        mDependency.injectMockDependency(AssistManager.class);
+        mDependency.injectMockDependency(OverviewProxyService.class);
+        mDependency.injectMockDependency(NavigationModeController.class);
+        mDependency.injectMockDependency(StatusBarStateController.class);
+        mDependency.injectMockDependency(KeyguardStateController.class);
+
         mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
         NavigationBarView navBar = spy(new NavigationBarView(mContext, null));
         when(navBar.getCurrentView()).thenReturn(navBar);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index a0d264d..2254234 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -33,6 +33,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -71,6 +72,7 @@
 
     @Before
     public void setup() {
+        mDependency.injectMockDependency(BubbleController.class);
         mHeadsUpManager = new HeadsUpManager(mContext) {};
 
         when(mNotificationEntryManager.getPendingNotificationsIterator())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
index dd274c7..493b74d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
@@ -30,6 +30,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -57,6 +58,7 @@
 
     @Before
     public void setup() {
+        mDependency.injectMockDependency(BubbleController.class);
         initializeGroupManager();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 95e9e67..cff6635 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -60,6 +60,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.InjectionInflationController;
 
@@ -115,6 +116,7 @@
     private FalsingManager mFalsingManager;
     @Mock
     private KeyguardBypassController mKeyguardBypassController;
+    @Mock private DozeParameters mDozeParameters;
     private NotificationPanelView mNotificationPanelView;
 
     @Before
@@ -129,10 +131,11 @@
         mDependency.injectMockDependency(ConfigurationController.class);
         mDependency.injectMockDependency(ZenModeController.class);
         NotificationWakeUpCoordinator coordinator =
-                new NotificationWakeUpCoordinator(mContext,
+                new NotificationWakeUpCoordinator(
                         mock(HeadsUpManagerPhone.class),
                         new StatusBarStateControllerImpl(),
-                        mKeyguardBypassController);
+                        mKeyguardBypassController,
+                        mDozeParameters);
         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
                 mContext,
                 coordinator,
@@ -141,7 +144,7 @@
                 mStatusBarStateController,
                 new FalsingManagerFake());
         mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
-                mKeyguardBypassController);
+                mKeyguardBypassController, mStatusBarStateController);
         mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
         mNotificationPanelView.setBar(mPanelBar);
 
@@ -218,7 +221,8 @@
     private class TestableNotificationPanelView extends NotificationPanelView {
         TestableNotificationPanelView(NotificationWakeUpCoordinator coordinator,
                 PulseExpansionHandler expansionHandler,
-                KeyguardBypassController bypassController) {
+                KeyguardBypassController bypassController,
+                SysuiStatusBarStateController statusBarStateController) {
             super(
                     NotificationPanelViewTest.this.mContext,
                     null,
@@ -235,7 +239,10 @@
                     new NotificationEntryManager(new NotificationData(mock(
                             NotificationSectionsFeatureManager.class), mock(NotifLog.class)),
                             mock(NotifLog.class)),
-                    mock(DozeLog.class));
+                    mock(KeyguardStateController.class),
+                    statusBarStateController,
+                    mock(DozeLog.class),
+                    mDozeParameters);
             mNotificationStackScroller = mNotificationStackScrollLayout;
             mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView;
             mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 214e26a..f3ff7ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -133,6 +133,20 @@
     }
 
     @Test
+    public void transitionToOff() {
+        mScrimController.transitionTo(ScrimState.OFF);
+        mScrimController.finishAnimationsImmediately();
+
+        assertScrimAlpha(OPAQUE /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
+    }
+
+    @Test
     public void transitionToAod_withRegularWallpaper() {
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index aafcdd0..3e07cff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -92,6 +93,7 @@
         MockitoAnnotations.initMocks(this);
         mDependency.injectMockDependency(StatusBarWindowController.class);
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
+        mDependency.injectMockDependency(NotificationMediaManager.class);
         mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
         mDependency.injectTestDependency(KeyguardStateController.class, mKeyguardStateController);
         when(mLockIconContainer.getParent()).thenReturn(mock(ViewGroup.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 266abcf..d8a68b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -140,7 +140,7 @@
         when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1));
         when(mContentIntent.getIntent()).thenReturn(mContentIntentInner);
 
-        mNotificationTestHelper = new NotificationTestHelper(mContext);
+        mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
 
         // Create standard notification with contentIntent
         mNotificationRow = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 24ec109..210c9d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -35,21 +35,37 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
+import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationAlertingManager;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper()
@@ -63,11 +79,33 @@
 
     @Before
     public void setup() {
+        NotificationRemoteInputManager notificationRemoteInputManager =
+                mock(NotificationRemoteInputManager.class);
+        when(notificationRemoteInputManager.getController())
+                .thenReturn(mock(RemoteInputController.class));
         mMetricsLogger = new FakeMetricsLogger();
         mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
         mCommandQueue = new CommandQueue(mContext);
         mContext.putComponent(CommandQueue.class, mCommandQueue);
+        mDependency.injectTestDependency(StatusBarStateController.class,
+                mock(SysuiStatusBarStateController.class));
         mDependency.injectTestDependency(ShadeController.class, mShadeController);
+        mDependency.injectTestDependency(NotificationRemoteInputManager.class,
+                notificationRemoteInputManager);
+        mDependency.injectMockDependency(NotificationViewHierarchyManager.class);
+        mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class);
+        mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
+        mDependency.injectMockDependency(NotificationInterruptionStateProvider.class);
+        mDependency.injectMockDependency(NotificationMediaManager.class);
+        mDependency.injectMockDependency(VisualStabilityManager.class);
+        mDependency.injectMockDependency(NotificationGutsManager.class);
+        mDependency.injectMockDependency(StatusBarWindowController.class);
+        mDependency.injectMockDependency(InitController.class);
+        NotificationData notificationData = mock(NotificationData.class);
+        when(notificationData.getNotificationsForCurrentUser()).thenReturn(new ArrayList<>());
+        NotificationEntryManager entryManager =
+                mDependency.injectMockDependency(NotificationEntryManager.class);
+        when(entryManager.getNotificationData()).thenReturn(notificationData);
 
         StatusBarWindowView statusBarWindowView = mock(StatusBarWindowView.class);
         when(statusBarWindowView.getResources()).thenReturn(mContext.getResources());
@@ -77,7 +115,7 @@
                 mock(DozeScrimController.class), mock(ScrimController.class),
                 mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class),
                 mock(NotificationAlertingManager.class),
-                mock(NotificationRowBinderImpl.class));
+                mock(NotificationRowBinderImpl.class), mock(KeyguardStateController.class));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 4b6ca56..a65f5a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -24,18 +24,19 @@
 import static org.mockito.internal.verification.VerificationModeFactory.times;
 
 import android.content.Intent;
-import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -47,14 +48,13 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
-    @Mock private NotificationPresenter mPresenter;
-    @Mock private UserManager mUserManager;
-
-    // Dependency mocks:
     @Mock private NotificationEntryManager mEntryManager;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private ShadeController mShadeController;
     @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
+    @Mock private KeyguardStateController mKeyguardStateController;
+    @Mock private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock private ActivityStarter mActivityStarter;
 
     private int mCurrentUserId = 0;
     private StatusBarRemoteInputCallback mRemoteInputCallback;
@@ -71,7 +71,9 @@
         mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
 
         mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
-                mock(NotificationGroupManager.class)));
+                mock(NotificationGroupManager.class), mNotificationLockscreenUserManager,
+                mKeyguardStateController, mStatusBarStateController, mActivityStarter,
+                mShadeController));
         mRemoteInputCallback.mChallengeReceiver = mRemoteInputCallback.new ChallengeReceiver();
     }
 
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 12e9be1..8f1b6017 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
@@ -220,6 +220,7 @@
     @Mock private StatusBarWindowViewController.Builder mStatusBarWindowViewControllerBuilder;
     @Mock private StatusBarWindowViewController mStatusBarWindowViewController;
     @Mock private NotifLog mNotifLog;
+    @Mock private DozeParameters mDozeParameters;
 
     @Before
     public void setup() throws Exception {
@@ -286,6 +287,7 @@
                 .thenReturn(mStatusBarWindowViewController);
 
         mStatusBar = new StatusBar(
+                mContext,
                 mLightBarController,
                 mAutoHideController,
                 mKeyguardUpdateMonitor,
@@ -342,10 +344,10 @@
                 configurationController,
                 mStatusBarWindowController,
                 mStatusBarWindowViewControllerBuilder,
-                mNotifLog);
+                mNotifLog,
+                mDozeParameters);
         // TODO: we should be able to call mStatusBar.start() and have all the below values
         // initialized automatically.
-        mStatusBar.mContext = mContext;
         mStatusBar.mComponents = mContext.getComponents();
         mStatusBar.mStatusBarKeyguardViewManager = mStatusBarKeyguardViewManager;
         mStatusBar.mStatusBarWindow = mStatusBarWindowView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
index 4ffaeae..a21a658 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
@@ -32,7 +32,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.colorextraction.ColorExtractor;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
@@ -48,20 +50,15 @@
 @SmallTest
 public class StatusBarWindowControllerTest extends SysuiTestCase {
 
-    @Mock
-    private WindowManager mWindowManager;
-    @Mock
-    private DozeParameters mDozeParameters;
-    @Mock
-    private ViewGroup mStatusBarView;
-    @Mock
-    private IActivityManager mActivityManager;
-    @Mock
-    private SysuiStatusBarStateController mStatusBarStateController;
-    @Mock
-    private ConfigurationController mConfigurationController;
-    @Mock
-    private KeyguardBypassController mKeyguardBypassController;
+    @Mock private WindowManager mWindowManager;
+    @Mock private DozeParameters mDozeParameters;
+    @Mock private ViewGroup mStatusBarView;
+    @Mock private IActivityManager mActivityManager;
+    @Mock private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock private ConfigurationController mConfigurationController;
+    @Mock private KeyguardBypassController mKeyguardBypassController;
+    @Mock private SysuiColorExtractor mColorExtractor;
+    @Mock ColorExtractor.GradientColors mGradientColors;
 
     private StatusBarWindowController mStatusBarWindowController;
 
@@ -69,10 +66,11 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+        when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
         mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
                 mActivityManager, mDozeParameters, mStatusBarStateController,
-                mConfigurationController, mKeyguardBypassController);
+                mConfigurationController, mKeyguardBypassController, mColorExtractor);
         mStatusBarWindowController.add(mStatusBarView, 100 /* height */);
     }
 
@@ -96,9 +94,6 @@
 
     @Test
     public void testOnThemeChanged_doesntCrash() {
-        mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
-                mActivityManager, mDozeParameters, mStatusBarStateController,
-                mConfigurationController, mKeyguardBypassController);
         mStatusBarWindowController.onThemeChanged();
     }
 
@@ -109,9 +104,6 @@
 
     @Test
     public void testSetForcePluginOpen_beforeStatusBarInitialization() {
-        mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
-                mActivityManager, mDozeParameters, mStatusBarStateController,
-                mConfigurationController, mKeyguardBypassController);
         mStatusBarWindowController.setForcePluginOpen(true);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
index 9f4dfb4..7c1dfa6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
@@ -34,9 +34,11 @@
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.InjectionInflationController;
 
@@ -61,11 +63,14 @@
     @Mock private PluginManager mPluginManager;
     @Mock private TunerService mTunerService;
     @Mock private DragDownHelper mDragDownHelper;
+    @Mock private KeyguardStateController mKeyguardStateController;
+    @Mock private SysuiStatusBarStateController mStatusBarStateController;
     @Mock private ShadeController mShadeController;
     @Mock private NotificationLockscreenUserManager mNotificationLockScreenUserManager;
     @Mock private NotificationEntryManager mNotificationEntryManager;
     @Mock private StatusBar mStatusBar;
     @Mock private DozeLog mDozeLog;
+    @Mock private DozeParameters mDozeParameters;
 
     @Before
     public void setUp() {
@@ -88,7 +93,10 @@
                 mTunerService,
                 mNotificationLockScreenUserManager,
                 mNotificationEntryManager,
-                mDozeLog)
+                mKeyguardStateController,
+                mStatusBarStateController,
+                mDozeLog,
+                mDozeParameters)
                 .setShadeController(mShadeController)
                 .setStatusBarWindowView(mView)
                 .build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
index d16dc16..943674a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
@@ -40,6 +40,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.recents.OverviewProxyService;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -68,6 +69,7 @@
         MockitoAnnotations.initMocks(this);
         mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
         mBubbleController = mDependency.injectMockDependency(BubbleController.class);
+        mDependency.injectMockDependency(OverviewProxyService.class);
         TestableLooper.get(this).runWithLooper(() -> {
             mKeyButtonView = new KeyButtonView(mContext, null, 0, mInputManager);
         });
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
new file mode 100644
index 0000000..e57bbc1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.policy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class KeyguardStateControllerTest extends SysuiTestCase {
+
+    @Mock
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock
+    private LockPatternUtils mLockPatternUtils;
+    private KeyguardStateController mKeyguardStateController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mKeyguardStateController = new KeyguardStateControllerImpl(mContext,
+                mKeyguardUpdateMonitor, mLockPatternUtils);
+    }
+
+    @Test
+    public void testAddCallback_registersListener() {
+        verify(mKeyguardUpdateMonitor).registerCallback(any());
+    }
+
+    @Test
+    public void testIsShowing() {
+        assertThat(mKeyguardStateController.isShowing()).isFalse();
+        mKeyguardStateController.notifyKeyguardState(true /* showing */, false /* occluded */);
+        assertThat(mKeyguardStateController.isShowing()).isTrue();
+    }
+
+    @Test
+    public void testIsMethodSecure() {
+        assertThat(mKeyguardStateController.isMethodSecure()).isFalse();
+
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+        ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+        assertThat(mKeyguardStateController.isMethodSecure()).isTrue();
+    }
+
+    @Test
+    public void testIsOccluded() {
+        assertThat(mKeyguardStateController.isOccluded()).isFalse();
+        mKeyguardStateController.notifyKeyguardState(false /* showing */, true /* occluded */);
+        assertThat(mKeyguardStateController.isOccluded()).isTrue();
+    }
+
+    @Test
+    public void testCanSkipLockScreen() {
+        // Can skip because LockPatternUtils#isSecure is false
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+
+        // Cannot skip after there's a password/pin/pattern
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+        ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse();
+
+        // Unless user is authenticated
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+        ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+        assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+    }
+
+    @Test
+    public void testIsUnlocked() {
+        // Is unlocked whenever the keyguard is not showing
+        assertThat(mKeyguardStateController.isShowing()).isFalse();
+        assertThat(mKeyguardStateController.isUnlocked()).isTrue();
+
+        // Unlocked if showing, but insecure
+        mKeyguardStateController.notifyKeyguardState(true /* showing */, false /* occluded */);
+        assertThat(mKeyguardStateController.isUnlocked()).isTrue();
+
+        // Locked if showing, and requires password
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+        ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+        assertThat(mKeyguardStateController.isUnlocked()).isFalse();
+
+        // But unlocked after #getUserCanSkipBouncer allows it
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+        ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+        assertThat(mKeyguardStateController.isUnlocked()).isTrue();
+    }
+
+    @Test
+    public void testIsTrusted() {
+        assertThat(mKeyguardStateController.isTrusted()).isFalse();
+
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+        ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+
+        assertThat(mKeyguardStateController.isTrusted()).isTrue();
+    }
+
+    @Test
+    public void testCallbacksAreInvoked() {
+        KeyguardStateController.Callback callback = mock(KeyguardStateController.Callback.class);
+        mKeyguardStateController.addCallback(callback);
+
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+        ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+
+        verify(callback).onUnlockedChanged();
+    }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index bc468bf..390e812 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -106,7 +106,8 @@
 
     @Test
     public void testSendRemoteInput_intentContainsResultsAndSource() throws Exception {
-        ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
+        ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency)
+                .createRow();
         RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
 
         setTestPendingIntent(view);
@@ -127,7 +128,7 @@
 
     private UserHandle getTargetInputMethodUser(UserHandle fromUser, UserHandle toUser)
             throws Exception {
-        ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow(
+        ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
                 DUMMY_MESSAGE_APP_PKG,
                 UserHandle.getUid(fromUser.getIdentifier(), DUMMY_MESSAGE_APP_ID),
                 toUser);
@@ -169,7 +170,8 @@
 
     @Test
     public void testNoCrashWithoutVisibilityListener() throws Exception {
-        ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
+        ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency)
+                .createRow();
         RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
 
         view.setOnVisibilityChangedListener(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 0d56cbe..b5e4cb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -52,6 +52,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -115,6 +116,7 @@
         });
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectMockDependency(ShadeController.class);
+        mDependency.injectMockDependency(NotificationRemoteInputManager.class);
         mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
         mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
 
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 5b826b1..b0e401b 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -2584,15 +2584,15 @@
 
     // ACTION: Logged when trampoline activity finishes.
     // TIME: Indicates total time taken by trampoline activity to finish in MS.
-    PROVISIONING_TRAMPOLINE_ACTIVITY_TIME_MS = 523;
+    PROVISIONING_TRAMPOLINE_ACTIVITY_TIME_MS = 523 [deprecated=true];
 
     // ACTION: Logged when encryption activity finishes.
     // TIME: Indicates total time taken by post encryption activity to finish in MS.
-    PROVISIONING_POST_ENCRYPTION_ACTIVITY_TIME_MS = 524;
+    PROVISIONING_POST_ENCRYPTION_ACTIVITY_TIME_MS = 524 [deprecated=true];
 
     // ACTION: Logged when finalization activity finishes.
     // TIME: Indicates time taken by finalization activity to finish in MS.
-    PROVISIONING_FINALIZATION_ACTIVITY_TIME_MS = 525;
+    PROVISIONING_FINALIZATION_ACTIVITY_TIME_MS = 525 [deprecated=true];
 
     // OPEN: Settings Support > Phone/Chat -> Disclaimer
     DIALOG_SUPPORT_DISCLAIMER = 526;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 91269c7..e909e7a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,13 +16,6 @@
 
 package com.android.server.accessibility;
 
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK;
-
 import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
@@ -50,8 +43,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
-import android.graphics.Point;
-import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.display.DisplayManager;
 import android.hardware.fingerprint.IFingerprintService;
@@ -119,7 +110,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -136,6 +126,7 @@
  */
 public class AccessibilityManagerService extends IAccessibilityManager.Stub
         implements AbstractAccessibilityServiceConnection.SystemSupport,
+        AccessibilityUserState.ServiceInfoChangeListener,
         AccessibilityWindowManager.AccessibilityEventSender,
         AccessibilitySecurityPolicy.AccessibilityUserManager {
 
@@ -178,12 +169,6 @@
     private final SimpleStringSplitter mStringColonSplitter =
             new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
 
-    private final Rect mTempRect = new Rect();
-
-    private final Rect mTempRect1 = new Rect();
-
-    private final Point mTempPoint = new Point();
-
     private final PackageManager mPackageManager;
 
     private final PowerManager mPowerManager;
@@ -226,7 +211,7 @@
     private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients =
             new RemoteCallbackList<>();
 
-    private final SparseArray<UserState> mUserStates = new SparseArray<>();
+    private final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>();
 
     private final UiAutomationManager mUiAutomationManager = new UiAutomationManager(mLock);
 
@@ -237,7 +222,7 @@
 
     private boolean mIsAccessibilityButtonShown;
 
-    private UserState getCurrentUserStateLocked() {
+    private AccessibilityUserState getCurrentUserStateLocked() {
         return getUserStateLocked(mCurrentUserId);
     }
 
@@ -293,6 +278,11 @@
         return mIsAccessibilityButtonShown;
     }
 
+    @Override
+    public void onServiceInfoChangedLocked(AccessibilityUserState userState) {
+        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+    }
+
     @Nullable
     public FingerprintGestureDispatcher getFingerprintGestureDispatcher() {
         return mFingerprintGestureDispatcher;
@@ -307,40 +297,40 @@
         }
     }
 
-    private UserState getUserState(int userId) {
+    private AccessibilityUserState getUserState(int userId) {
         synchronized (mLock) {
             return getUserStateLocked(userId);
         }
     }
 
-    private UserState getUserStateLocked(int userId) {
-        UserState state = mUserStates.get(userId);
+    @NonNull
+    private AccessibilityUserState getUserStateLocked(int userId) {
+        AccessibilityUserState state = mUserStates.get(userId);
         if (state == null) {
-            state = new UserState(userId);
+            state = new AccessibilityUserState(userId, mContext, this);
             mUserStates.put(userId, state);
         }
         return state;
     }
 
     boolean getBindInstantServiceAllowed(int userId) {
-        final UserState userState = getUserState(userId);
-        if (userState == null) return false;
-        return userState.getBindInstantServiceAllowed();
+        synchronized (mLock) {
+            final AccessibilityUserState userState = getUserStateLocked(userId);
+            return userState.getBindInstantServiceAllowedLocked();
+        }
     }
 
     void setBindInstantServiceAllowed(int userId, boolean allowed) {
-        UserState userState;
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
+                "setBindInstantServiceAllowed");
         synchronized (mLock) {
-            userState = getUserState(userId);
-            if (userState == null) {
-                if (!allowed) {
-                    return;
-                }
-                userState = new UserState(userId);
-                mUserStates.put(userId, userState);
+            final AccessibilityUserState userState = getUserStateLocked(userId);
+            if (allowed != userState.getBindInstantServiceAllowedLocked()) {
+                userState.setBindInstantServiceAllowedLocked(allowed);
+                onUserStateChangedLocked(userState);
             }
         }
-        userState.setBindInstantServiceAllowed(allowed);
     }
 
     private void registerBroadcastReceivers() {
@@ -354,7 +344,7 @@
                         return;
                     }
                     // We will update when the automation service dies.
-                    UserState userState = getCurrentUserStateLocked();
+                    AccessibilityUserState userState = getCurrentUserStateLocked();
                     // We have to reload the installed services since some services may
                     // have different attributes, resolve info (does not support equals),
                     // etc. Remove them then to force reload.
@@ -376,8 +366,8 @@
                     if (userId != mCurrentUserId) {
                         return;
                     }
-                    UserState userState = getUserStateLocked(userId);
-                    boolean reboundAService = userState.mBindingServices.removeIf(
+                    AccessibilityUserState userState = getUserStateLocked(userId);
+                    boolean reboundAService = userState.getBindingServicesLocked().removeIf(
                             component -> component != null
                                     && component.getPackageName().equals(packageName));
                     if (reboundAService) {
@@ -395,14 +385,14 @@
                     if (userId != mCurrentUserId) {
                         return;
                     }
-                    UserState userState = getUserStateLocked(userId);
+                    AccessibilityUserState userState = getUserStateLocked(userId);
                     Iterator<ComponentName> it = userState.mEnabledServices.iterator();
                     while (it.hasNext()) {
                         ComponentName comp = it.next();
                         String compPkg = comp.getPackageName();
                         if (compPkg.equals(packageName)) {
                             it.remove();
-                            userState.mBindingServices.remove(comp);
+                            userState.getBindingServicesLocked().remove(comp);
                             // Update the enabled services setting.
                             persistComponentNamesToSettingLocked(
                                     Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
@@ -430,7 +420,7 @@
                     if (userId != mCurrentUserId) {
                         return false;
                     }
-                    UserState userState = getUserStateLocked(userId);
+                    AccessibilityUserState userState = getUserStateLocked(userId);
                     Iterator<ComponentName> it = userState.mEnabledServices.iterator();
                     while (it.hasNext()) {
                         ComponentName comp = it.next();
@@ -441,7 +431,7 @@
                                     return true;
                                 }
                                 it.remove();
-                                userState.mBindingServices.remove(comp);
+                                userState.getBindingServicesLocked().remove(comp);
                                 persistComponentNamesToSettingLocked(
                                         Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                                         userState.mEnabledServices, userId);
@@ -478,7 +468,7 @@
                 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
                     // We will update when the automation service dies.
                     synchronized (mLock) {
-                        UserState userState = getCurrentUserStateLocked();
+                        AccessibilityUserState userState = getCurrentUserStateLocked();
                         if (readConfigurationForUserStateLocked(userState)) {
                             onUserStateChangedLocked(userState);
                         }
@@ -509,7 +499,7 @@
             // If the client is from a process that runs across users such as
             // the system UI or the system we add it to the global state that
             // is shared across users.
-            UserState userState = getUserStateLocked(resolvedUserId);
+            AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
             Client client = new Client(callback, Binder.getCallingUid(), userState);
             if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) {
                 mGlobalClients.register(callback, client);
@@ -517,7 +507,7 @@
                     Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
                 }
                 return IntPair.of(
-                        userState.getClientState(),
+                        getClientStateLocked(userState),
                         client.mLastSentRelevantEventTypes);
             } else {
                 userState.mUserClients.register(callback, client);
@@ -529,7 +519,7 @@
                             + " and userId:" + mCurrentUserId);
                 }
                 return IntPair.of(
-                        (resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0,
+                        (resolvedUserId == mCurrentUserId) ? getClientStateLocked(userState) : 0,
                         client.mLastSentRelevantEventTypes);
             }
         }
@@ -644,7 +634,7 @@
                     .resolveCallingUserIdEnforcingPermissionsLocked(userId);
 
             // The automation service can suppress other services.
-            final UserState userState = getUserStateLocked(resolvedUserId);
+            final AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
             if (mUiAutomationManager.suppressingAccessibilityServicesLocked()) {
                 return Collections.emptyList();
             }
@@ -754,15 +744,15 @@
         }
         synchronized (mLock) {
             // Set the temporary state.
-            UserState userState = getCurrentUserStateLocked();
+            AccessibilityUserState userState = getCurrentUserStateLocked();
 
-            userState.mIsTouchExplorationEnabled = touchExplorationEnabled;
-            userState.mIsDisplayMagnificationEnabled = false;
-            userState.mIsNavBarMagnificationEnabled = false;
-            userState.mIsAutoclickEnabled = false;
+            userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
+            userState.setDisplayMagnificationEnabledLocked(false);
+            userState.setNavBarMagnificationEnabledLocked(false);
+            userState.setAutoclickEnabledLocked(false);
             userState.mEnabledServices.clear();
             userState.mEnabledServices.add(service);
-            userState.mBindingServices.clear();
+            userState.getBindingServicesLocked().clear();
             userState.mTouchExplorationGrantedServices.clear();
             userState.mTouchExplorationGrantedServices.add(service);
 
@@ -943,7 +933,7 @@
             }
 
             // Disconnect from services for the old user.
-            UserState oldUserState = getCurrentUserStateLocked();
+            AccessibilityUserState oldUserState = getCurrentUserStateLocked();
             oldUserState.onSwitchToAnotherUserLocked();
 
             // Disable the local managers for the old user.
@@ -960,7 +950,7 @@
             // The user changed.
             mCurrentUserId = userId;
 
-            UserState userState = getCurrentUserStateLocked();
+            AccessibilityUserState userState = getCurrentUserStateLocked();
 
             readConfigurationForUserStateLocked(userState);
             // Even if reading did not yield change, we have to update
@@ -979,8 +969,8 @@
 
     private void announceNewUserIfNeeded() {
         synchronized (mLock) {
-            UserState userState = getCurrentUserStateLocked();
-            if (userState.isHandlingAccessibilityEvents()) {
+            AccessibilityUserState userState = getCurrentUserStateLocked();
+            if (userState.isHandlingAccessibilityEventsLocked()) {
                 UserManager userManager = (UserManager) mContext.getSystemService(
                         Context.USER_SERVICE);
                 String message = mContext.getString(R.string.user_switched,
@@ -997,7 +987,7 @@
         synchronized (mLock) {
             int parentUserId = mSecurityPolicy.resolveProfileParentLocked(userId);
             if (parentUserId == mCurrentUserId) {
-                UserState userState = getUserStateLocked(mCurrentUserId);
+                AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
                 onUserStateChangedLocked(userState);
             }
         }
@@ -1015,7 +1005,7 @@
         readComponentNamesFromStringLocked(oldSetting, mTempComponentNameSet, false);
         readComponentNamesFromStringLocked(newSetting, mTempComponentNameSet, true);
 
-        UserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
+        AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
         userState.mEnabledServices.clear();
         userState.mEnabledServices.addAll(mTempComponentNameSet);
         persistComponentNamesToSettingLocked(
@@ -1025,6 +1015,10 @@
         onUserStateChangedLocked(userState);
     }
 
+    private int getClientStateLocked(AccessibilityUserState userState) {
+        return userState.getClientStateLocked(mUiAutomationManager.isUiAutomationRunningLocked());
+    }
+
     private InteractionBridge getInteractionBridge() {
         synchronized (mLock) {
             if (mInteractionBridge == null) {
@@ -1044,7 +1038,7 @@
         //       gestures to avoid user frustration when different
         //       behavior is observed from different combinations of
         //       enabled accessibility services.
-        UserState state = getCurrentUserStateLocked();
+        AccessibilityUserState state = getCurrentUserStateLocked();
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
             AccessibilityServiceConnection service = state.mBoundServices.get(i);
             if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) {
@@ -1056,7 +1050,7 @@
     }
 
     private void notifyClearAccessibilityCacheLocked() {
-        UserState state = getCurrentUserStateLocked();
+        AccessibilityUserState state = getCurrentUserStateLocked();
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
             AccessibilityServiceConnection service = state.mBoundServices.get(i);
             service.notifyClearAccessibilityNodeInfoCache();
@@ -1065,25 +1059,17 @@
 
     private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
             float scale, float centerX, float centerY) {
-        final UserState state = getCurrentUserStateLocked();
+        final AccessibilityUserState state = getCurrentUserStateLocked();
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
             final AccessibilityServiceConnection service = state.mBoundServices.get(i);
             service.notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
         }
     }
 
-    private void notifySoftKeyboardShowModeChangedLocked(int showMode) {
-        final UserState state = getCurrentUserStateLocked();
-        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
-            final AccessibilityServiceConnection service = state.mBoundServices.get(i);
-            service.notifySoftKeyboardShowModeChangedLocked(showMode);
-        }
-    }
-
     private void notifyAccessibilityButtonClickedLocked(int displayId) {
-        final UserState state = getCurrentUserStateLocked();
+        final AccessibilityUserState state = getCurrentUserStateLocked();
 
-        int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0;
+        int potentialTargets = state.isNavBarMagnificationEnabledLocked() ? 1 : 0;
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
             final AccessibilityServiceConnection service = state.mBoundServices.get(i);
             if (service.mRequestAccessibilityButton) {
@@ -1095,7 +1081,7 @@
             return;
         }
         if (potentialTargets == 1) {
-            if (state.mIsNavBarMagnificationEnabled) {
+            if (state.isNavBarMagnificationEnabledLocked()) {
                 mMainHandler.sendMessage(obtainMessage(
                         AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
                         displayId));
@@ -1110,13 +1096,13 @@
                 }
             }
         } else {
-            if (state.mServiceAssignedToAccessibilityButton == null
-                    && !state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+            if (state.getServiceAssignedToAccessibilityButtonLocked() == null
+                    && !state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
                 mMainHandler.sendMessage(obtainMessage(
                         AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
                         displayId));
-            } else if (state.mIsNavBarMagnificationEnabled
-                    && state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+            } else if (state.isNavBarMagnificationEnabledLocked()
+                    && state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
                 mMainHandler.sendMessage(obtainMessage(
                         AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
                         displayId));
@@ -1125,7 +1111,7 @@
                 for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
                     final AccessibilityServiceConnection service = state.mBoundServices.get(i);
                     if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
-                            state.mServiceAssignedToAccessibilityButton))) {
+                            state.getServiceAssignedToAccessibilityButtonLocked()))) {
                         service.notifyAccessibilityButtonClickedLocked(displayId);
                         return;
                     }
@@ -1154,7 +1140,7 @@
     }
 
     private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) {
-        final UserState state = getCurrentUserStateLocked();
+        final AccessibilityUserState state = getCurrentUserStateLocked();
         mIsAccessibilityButtonShown = available;
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
             final AccessibilityServiceConnection clientConnection = state.mBoundServices.get(i);
@@ -1165,7 +1151,7 @@
         }
     }
 
-    private boolean readInstalledAccessibilityServiceLocked(UserState userState) {
+    private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) {
         mTempAccessibilityServiceInfoList.clear();
 
         int flags = PackageManager.GET_SERVICES
@@ -1174,7 +1160,7 @@
                 | PackageManager.MATCH_DIRECT_BOOT_AWARE
                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
-        if (userState.getBindInstantServiceAllowed()) {
+        if (userState.getBindInstantServiceAllowedLocked()) {
             flags |= PackageManager.MATCH_INSTANT;
         }
 
@@ -1209,7 +1195,7 @@
         return false;
     }
 
-    private boolean readInstalledAccessibilityShortcutLocked(UserState userState) {
+    private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) {
         final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager
                 .getInstance(mContext).getInstalledAccessibilityShortcutListAsUser(
                         mContext, mCurrentUserId);
@@ -1221,7 +1207,7 @@
         return false;
     }
 
-    private boolean readEnabledAccessibilityServicesLocked(UserState userState) {
+    private boolean readEnabledAccessibilityServicesLocked(AccessibilityUserState userState) {
         mTempComponentNameSet.clear();
         readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                 userState.mUserId, mTempComponentNameSet);
@@ -1236,7 +1222,7 @@
     }
 
     private boolean readTouchExplorationGrantedAccessibilityServicesLocked(
-            UserState userState) {
+            AccessibilityUserState userState) {
         mTempComponentNameSet.clear();
         readComponentNamesFromSettingLocked(
                 Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
@@ -1261,7 +1247,7 @@
     private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event,
             boolean isDefault) {
         try {
-            UserState state = getCurrentUserStateLocked();
+            AccessibilityUserState state = getCurrentUserStateLocked();
             for (int i = 0, count = state.mBoundServices.size(); i < count; i++) {
                 AccessibilityServiceConnection service = state.mBoundServices.get(i);
 
@@ -1276,7 +1262,7 @@
         }
     }
 
-    private void updateRelevantEventsLocked(UserState userState) {
+    private void updateRelevantEventsLocked(AccessibilityUserState userState) {
         mMainHandler.post(() -> {
             broadcastToClients(userState, ignoreRemoteException(client -> {
                 int relevantEventTypes;
@@ -1296,7 +1282,7 @@
         });
     }
 
-    private int computeRelevantEventTypesLocked(UserState userState, Client client) {
+    private int computeRelevantEventTypesLocked(AccessibilityUserState userState, Client client) {
         int relevantEventTypes = 0;
 
         int serviceCount = userState.mBoundServices.size();
@@ -1342,20 +1328,11 @@
     }
 
     private void broadcastToClients(
-            UserState userState, Consumer<Client> clientAction) {
+            AccessibilityUserState userState, Consumer<Client> clientAction) {
         mGlobalClients.broadcastForEachCookie(clientAction);
         userState.mUserClients.broadcastForEachCookie(clientAction);
     }
 
-    private void unbindAllServicesLocked(UserState userState) {
-        List<AccessibilityServiceConnection> services = userState.mBoundServices;
-        for (int count = services.size(); count > 0; count--) {
-            // When the service is unbound, it disappears from the list, so there's no need to
-            // keep track of the index
-            services.get(0).unbindLocked();
-        }
-    }
-
     /**
      * Populates a set with the {@link ComponentName}s stored in a colon
      * separated value setting for a given user.
@@ -1422,7 +1399,7 @@
         }
     }
 
-    private void updateServicesLocked(UserState userState) {
+    private void updateServicesLocked(AccessibilityUserState userState) {
         Map<ComponentName, AccessibilityServiceConnection> componentNameToServiceMap =
                 userState.mComponentNameToServiceMap;
         boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class)
@@ -1442,7 +1419,7 @@
             }
 
             // Wait for the binding if it is in process.
-            if (userState.mBindingServices.contains(componentName)) {
+            if (userState.getBindingServicesLocked().contains(componentName)) {
                 continue;
             }
             if (userState.mEnabledServices.contains(componentName)
@@ -1478,15 +1455,15 @@
         if (audioManager != null) {
             audioManager.setAccessibilityServiceUids(mTempIntArray);
         }
-        updateAccessibilityEnabledSetting(userState);
+        updateAccessibilityEnabledSettingLocked(userState);
     }
 
-    private void scheduleUpdateClientsIfNeededLocked(UserState userState) {
-        final int clientState = userState.getClientState();
-        if (userState.mLastSentClientState != clientState
+    private void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
+        final int clientState = getClientStateLocked(userState);
+        if (userState.getLastSentClientStateLocked() != clientState
                 && (mGlobalClients.getRegisteredCallbackCount() > 0
                         || userState.mUserClients.getRegisteredCallbackCount() > 0)) {
-            userState.mLastSentClientState = clientState;
+            userState.setLastSentClientStateLocked(clientState);
             mMainHandler.sendMessage(obtainMessage(
                     AccessibilityManagerService::sendStateToAllClients,
                     this, clientState, userState.mUserId));
@@ -1508,7 +1485,8 @@
                 client -> client.setState(clientState)));
     }
 
-    private void scheduleNotifyClientsOfServicesStateChangeLocked(UserState userState) {
+    private void scheduleNotifyClientsOfServicesStateChangeLocked(
+            AccessibilityUserState userState) {
         updateRecommendedUiTimeoutLocked(userState);
         mMainHandler.sendMessage(obtainMessage(
                 AccessibilityManagerService::sendServicesStateChanged,
@@ -1527,44 +1505,45 @@
                 client -> client.notifyServicesStateChanged(uiTimeout)));
     }
 
-    private void scheduleUpdateInputFilter(UserState userState) {
+    private void scheduleUpdateInputFilter(AccessibilityUserState userState) {
         mMainHandler.sendMessage(obtainMessage(
                 AccessibilityManagerService::updateInputFilter, this, userState));
     }
 
-    private void scheduleUpdateFingerprintGestureHandling(UserState userState) {
+    private void scheduleUpdateFingerprintGestureHandling(AccessibilityUserState userState) {
         mMainHandler.sendMessage(obtainMessage(
                 AccessibilityManagerService::updateFingerprintGestureHandling,
                 this, userState));
     }
 
-    private void updateInputFilter(UserState userState) {
+    private void updateInputFilter(AccessibilityUserState userState) {
         if (mUiAutomationManager.suppressingAccessibilityServicesLocked()) return;
 
         boolean setInputFilter = false;
         AccessibilityInputFilter inputFilter = null;
         synchronized (mLock) {
             int flags = 0;
-            if (userState.mIsDisplayMagnificationEnabled) {
+            if (userState.isDisplayMagnificationEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
             }
-            if (userState.mIsNavBarMagnificationEnabled) {
+            if (userState.isNavBarMagnificationEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
             }
             if (userHasMagnificationServicesLocked(userState)) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER;
             }
             // Touch exploration without accessibility makes no sense.
-            if (userState.isHandlingAccessibilityEvents() && userState.mIsTouchExplorationEnabled) {
+            if (userState.isHandlingAccessibilityEventsLocked()
+                    && userState.isTouchExplorationEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION;
             }
-            if (userState.mIsFilterKeyEventsEnabled) {
+            if (userState.isFilterKeyEventsEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
             }
-            if (userState.mIsAutoclickEnabled) {
+            if (userState.isAutoclickEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK;
             }
-            if (userState.mIsPerformGesturesEnabled) {
+            if (userState.isPerformGesturesEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
             }
             if (flags != 0) {
@@ -1597,8 +1576,8 @@
             String label = service.getServiceInfo().getResolveInfo()
                     .loadLabel(mContext.getPackageManager()).toString();
 
-            final UserState userState = getCurrentUserStateLocked();
-            if (userState.mIsTouchExplorationEnabled) {
+            final AccessibilityUserState userState = getCurrentUserStateLocked();
+            if (userState.isTouchExplorationEnabledLocked()) {
                 return;
             }
             if (mEnableTouchExplorationDialog != null
@@ -1608,40 +1587,40 @@
             mEnableTouchExplorationDialog = new AlertDialog.Builder(mContext)
                 .setIconAttribute(android.R.attr.alertDialogIcon)
                 .setPositiveButton(android.R.string.ok, new OnClickListener() {
-                     @Override
-                     public void onClick(DialogInterface dialog, int which) {
-                         // The user allowed the service to toggle touch exploration.
-                         userState.mTouchExplorationGrantedServices.add(service.mComponentName);
-                         persistComponentNamesToSettingLocked(
-                                 Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
-                                 userState.mTouchExplorationGrantedServices, userState.mUserId);
-                         // Enable touch exploration.
-                         userState.mIsTouchExplorationEnabled = true;
-                         final long identity = Binder.clearCallingIdentity();
-                         try {
-                             Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                                     Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1,
-                                     userState.mUserId);
-                         } finally {
-                             Binder.restoreCallingIdentity(identity);
-                         }
-                         onUserStateChangedLocked(userState);
-                     }
-                 })
-                 .setNegativeButton(android.R.string.cancel, new OnClickListener() {
-                     @Override
-                     public void onClick(DialogInterface dialog, int which) {
-                         dialog.dismiss();
-                     }
-                 })
-                 .setTitle(R.string.enable_explore_by_touch_warning_title)
-                 .setMessage(mContext.getString(
-                         R.string.enable_explore_by_touch_warning_message, label))
-                 .create();
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        // The user allowed the service to toggle touch exploration.
+                        userState.mTouchExplorationGrantedServices.add(service.mComponentName);
+                        persistComponentNamesToSettingLocked(
+                                Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+                                userState.mTouchExplorationGrantedServices, userState.mUserId);
+                        // Enable touch exploration.
+                        userState.setTouchExplorationEnabledLocked(true);
+                        final long identity = Binder.clearCallingIdentity();
+                        try {
+                            Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                                    Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1,
+                                    userState.mUserId);
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                        onUserStateChangedLocked(userState);
+                    }
+                })
+                .setNegativeButton(android.R.string.cancel, new OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        dialog.dismiss();
+                    }
+                })
+                .setTitle(R.string.enable_explore_by_touch_warning_title)
+                .setMessage(mContext.getString(
+                        R.string.enable_explore_by_touch_warning_message, label))
+                .create();
              mEnableTouchExplorationDialog.getWindow().setType(
                      WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
              mEnableTouchExplorationDialog.getWindow().getAttributes().privateFlags
-                     |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+                     |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
              mEnableTouchExplorationDialog.setCanceledOnTouchOutside(true);
              mEnableTouchExplorationDialog.show();
         }
@@ -1652,7 +1631,7 @@
      *
      * @param userState the new user state
      */
-    private void onUserStateChangedLocked(UserState userState) {
+    private void onUserStateChangedLocked(AccessibilityUserState userState) {
         // TODO: Remove this hack
         mInitialized = true;
         updateLegacyCapabilitiesLocked(userState);
@@ -1670,7 +1649,7 @@
         updateAccessibilityButtonTargetsLocked(userState);
     }
 
-    private void updateWindowsForAccessibilityCallbackLocked(UserState userState) {
+    private void updateWindowsForAccessibilityCallbackLocked(AccessibilityUserState userState) {
         // We observe windows for accessibility only if there is at least
         // one bound service that can retrieve window content that specified
         // it is interested in accessing such windows. For services that are
@@ -1702,7 +1681,7 @@
         }
     }
 
-    private void updateLegacyCapabilitiesLocked(UserState userState) {
+    private void updateLegacyCapabilitiesLocked(AccessibilityUserState userState) {
         // Up to JB-MR1 we had a white list with services that can enable touch
         // exploration. When a service is first started we show a dialog to the
         // use to get a permission to white list the service.
@@ -1724,20 +1703,20 @@
         }
     }
 
-    private void updatePerformGesturesLocked(UserState userState) {
+    private void updatePerformGesturesLocked(AccessibilityUserState userState) {
         final int serviceCount = userState.mBoundServices.size();
         for (int i = 0; i < serviceCount; i++) {
             AccessibilityServiceConnection service = userState.mBoundServices.get(i);
             if ((service.getCapabilities()
                     & AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) != 0) {
-                userState.mIsPerformGesturesEnabled = true;
+                userState.setPerformGesturesEnabledLocked(true);
                 return;
             }
         }
-        userState.mIsPerformGesturesEnabled = false;
+        userState.setPerformGesturesEnabledLocked(false);
     }
 
-    private void updateFilterKeyEventsLocked(UserState userState) {
+    private void updateFilterKeyEventsLocked(AccessibilityUserState userState) {
         final int serviceCount = userState.mBoundServices.size();
         for (int i = 0; i < serviceCount; i++) {
             AccessibilityServiceConnection service = userState.mBoundServices.get(i);
@@ -1745,14 +1724,14 @@
                     && (service.getCapabilities()
                             & AccessibilityServiceInfo
                             .CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) != 0) {
-                userState.mIsFilterKeyEventsEnabled = true;
+                userState.setFilterKeyEventsEnabledLocked(true);
                 return;
             }
         }
-        userState.mIsFilterKeyEventsEnabled = false;
+        userState.setFilterKeyEventsEnabledLocked(false);
     }
 
-    private boolean readConfigurationForUserStateLocked(UserState userState) {
+    private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) {
         boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState);
         somethingChanged |= readInstalledAccessibilityShortcutLocked(userState);
         somethingChanged |= readEnabledAccessibilityServicesLocked(userState);
@@ -1767,10 +1746,10 @@
         return somethingChanged;
     }
 
-    private void updateAccessibilityEnabledSetting(UserState userState) {
+    private void updateAccessibilityEnabledSettingLocked(AccessibilityUserState userState) {
         final long identity = Binder.clearCallingIdentity();
         final boolean isA11yEnabled = mUiAutomationManager.isUiAutomationRunningLocked()
-                || userState.isHandlingAccessibilityEvents();
+                || userState.isHandlingAccessibilityEventsLocked();
         try {
             Settings.Secure.putIntForUser(mContext.getContentResolver(),
                     Settings.Secure.ACCESSIBILITY_ENABLED,
@@ -1781,18 +1760,18 @@
         }
     }
 
-    private boolean readTouchExplorationEnabledSettingLocked(UserState userState) {
+    private boolean readTouchExplorationEnabledSettingLocked(AccessibilityUserState userState) {
         final boolean touchExplorationEnabled = Settings.Secure.getIntForUser(
                 mContext.getContentResolver(),
                 Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId) == 1;
-        if (touchExplorationEnabled != userState.mIsTouchExplorationEnabled) {
-            userState.mIsTouchExplorationEnabled = touchExplorationEnabled;
+        if (touchExplorationEnabled != userState.isTouchExplorationEnabledLocked()) {
+            userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
             return true;
         }
         return false;
     }
 
-    private boolean readMagnificationEnabledSettingsLocked(UserState userState) {
+    private boolean readMagnificationEnabledSettingsLocked(AccessibilityUserState userState) {
         final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser(
                 mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
@@ -1801,40 +1780,40 @@
                 mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
                 0, userState.mUserId) == 1;
-        if ((displayMagnificationEnabled != userState.mIsDisplayMagnificationEnabled)
-                || (navBarMagnificationEnabled != userState.mIsNavBarMagnificationEnabled)) {
-            userState.mIsDisplayMagnificationEnabled = displayMagnificationEnabled;
-            userState.mIsNavBarMagnificationEnabled = navBarMagnificationEnabled;
+        if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())
+                || (navBarMagnificationEnabled != userState.isNavBarMagnificationEnabledLocked())) {
+            userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled);
+            userState.setNavBarMagnificationEnabledLocked(navBarMagnificationEnabled);
             return true;
         }
         return false;
     }
 
-    private boolean readAutoclickEnabledSettingLocked(UserState userState) {
+    private boolean readAutoclickEnabledSettingLocked(AccessibilityUserState userState) {
         final boolean autoclickEnabled = Settings.Secure.getIntForUser(
                 mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
                 0, userState.mUserId) == 1;
-        if (autoclickEnabled != userState.mIsAutoclickEnabled) {
-            userState.mIsAutoclickEnabled = autoclickEnabled;
+        if (autoclickEnabled != userState.isAutoclickEnabledLocked()) {
+            userState.setAutoclickEnabledLocked(autoclickEnabled);
             return true;
         }
         return false;
     }
 
-    private boolean readHighTextContrastEnabledSettingLocked(UserState userState) {
+    private boolean readHighTextContrastEnabledSettingLocked(AccessibilityUserState userState) {
         final boolean highTextContrastEnabled = Settings.Secure.getIntForUser(
                 mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0,
                 userState.mUserId) == 1;
-        if (highTextContrastEnabled != userState.mIsTextHighContrastEnabled) {
-            userState.mIsTextHighContrastEnabled = highTextContrastEnabled;
+        if (highTextContrastEnabled != userState.isTextHighContrastEnabledLocked()) {
+            userState.setTextHighContrastEnabledLocked(highTextContrastEnabled);
             return true;
         }
         return false;
     }
 
-    private void updateTouchExplorationLocked(UserState userState) {
+    private void updateTouchExplorationLocked(AccessibilityUserState userState) {
         boolean enabled = mUiAutomationManager.isTouchExplorationEnabledLocked();
         final int serviceCount = userState.mBoundServices.size();
         for (int i = 0; i < serviceCount; i++) {
@@ -1844,8 +1823,8 @@
                 break;
             }
         }
-        if (enabled != userState.mIsTouchExplorationEnabled) {
-            userState.mIsTouchExplorationEnabled = enabled;
+        if (enabled != userState.isTouchExplorationEnabledLocked()) {
+            userState.setTouchExplorationEnabledLocked(enabled);
             final long identity = Binder.clearCallingIdentity();
             try {
                 Settings.Secure.putIntForUser(mContext.getContentResolver(),
@@ -1857,60 +1836,61 @@
         }
     }
 
-    private boolean readAccessibilityShortcutSettingLocked(UserState userState) {
+    private boolean readAccessibilityShortcutSettingLocked(AccessibilityUserState userState) {
         String componentNameToEnableString = AccessibilityShortcutController
                 .getTargetServiceComponentNameString(mContext, userState.mUserId);
         if ((componentNameToEnableString == null) || componentNameToEnableString.isEmpty()) {
-            if (userState.mServiceToEnableWithShortcut == null) {
+            if (userState.getServiceToEnableWithShortcutLocked() == null) {
                 return false;
             }
-            userState.mServiceToEnableWithShortcut = null;
+            userState.setServiceToEnableWithShortcutLocked(null);
             return true;
         }
         ComponentName componentNameToEnable =
             ComponentName.unflattenFromString(componentNameToEnableString);
         if ((componentNameToEnable != null)
-                && componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) {
+                && componentNameToEnable.equals(userState.getServiceToEnableWithShortcutLocked())) {
             return false;
         }
 
-        userState.mServiceToEnableWithShortcut = componentNameToEnable;
+        userState.setServiceToEnableWithShortcutLocked(componentNameToEnable);
         scheduleNotifyClientsOfServicesStateChangeLocked(userState);
         return true;
     }
 
-    private boolean readAccessibilityButtonSettingsLocked(UserState userState) {
+    private boolean readAccessibilityButtonSettingsLocked(AccessibilityUserState userState) {
         String componentId = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, userState.mUserId);
         if (TextUtils.isEmpty(componentId)) {
-            if ((userState.mServiceAssignedToAccessibilityButton == null)
-                    && !userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+            if ((userState.getServiceAssignedToAccessibilityButtonLocked() == null)
+                    && !userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
                 return false;
             }
-            userState.mServiceAssignedToAccessibilityButton = null;
-            userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+            userState.setServiceAssignedToAccessibilityButtonLocked(null);
+            userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(false);
             return true;
         }
 
         if (componentId.equals(MagnificationController.class.getName())) {
-            if (userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+            if (userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
                 return false;
             }
-            userState.mServiceAssignedToAccessibilityButton = null;
-            userState.mIsNavBarMagnificationAssignedToAccessibilityButton = true;
+            userState.setServiceAssignedToAccessibilityButtonLocked(null);
+            userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(true);
             return true;
         }
 
         ComponentName componentName = ComponentName.unflattenFromString(componentId);
-        if (Objects.equals(componentName, userState.mServiceAssignedToAccessibilityButton)) {
+        if (Objects.equals(componentName,
+                userState.getServiceAssignedToAccessibilityButtonLocked())) {
             return false;
         }
-        userState.mServiceAssignedToAccessibilityButton = componentName;
-        userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+        userState.setServiceAssignedToAccessibilityButtonLocked(componentName);
+        userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(false);
         return true;
     }
 
-    private boolean readUserRecommendedUiTimeoutSettingsLocked(UserState userState) {
+    private boolean readUserRecommendedUiTimeoutSettingsLocked(AccessibilityUserState userState) {
         final int nonInteractiveUiTimeout = Settings.Secure.getIntForUser(
                 mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS, 0,
@@ -1919,10 +1899,10 @@
                 mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, 0,
                 userState.mUserId);
-        if (nonInteractiveUiTimeout != userState.mUserNonInteractiveUiTimeout
-                || interactiveUiTimeout != userState.mUserInteractiveUiTimeout) {
-            userState.mUserNonInteractiveUiTimeout = nonInteractiveUiTimeout;
-            userState.mUserInteractiveUiTimeout = interactiveUiTimeout;
+        if (nonInteractiveUiTimeout != userState.getUserNonInteractiveUiTimeoutLocked()
+                || interactiveUiTimeout != userState.getUserInteractiveUiTimeoutLocked()) {
+            userState.setUserNonInteractiveUiTimeoutLocked(nonInteractiveUiTimeout);
+            userState.setUserInteractiveUiTimeoutLocked(interactiveUiTimeout);
             scheduleNotifyClientsOfServicesStateChangeLocked(userState);
             return true;
         }
@@ -1936,22 +1916,22 @@
      *
      * @param userState
      */
-    private void updateAccessibilityShortcutLocked(UserState userState) {
-        if (userState.mServiceToEnableWithShortcut == null) {
+    private void updateAccessibilityShortcutLocked(AccessibilityUserState userState) {
+        if (userState.getServiceToEnableWithShortcutLocked() == null) {
             return;
         }
         boolean shortcutServiceIsInstalled =
                 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
-                        .containsKey(userState.mServiceToEnableWithShortcut);
+                        .containsKey(userState.getServiceToEnableWithShortcutLocked());
         for (int i = 0; !shortcutServiceIsInstalled && (i < userState.mInstalledServices.size());
                 i++) {
             if (userState.mInstalledServices.get(i).getComponentName()
-                    .equals(userState.mServiceToEnableWithShortcut)) {
+                    .equals(userState.getServiceToEnableWithShortcutLocked())) {
                 shortcutServiceIsInstalled = true;
             }
         }
         if (!shortcutServiceIsInstalled) {
-            userState.mServiceToEnableWithShortcut = null;
+            userState.setServiceToEnableWithShortcutLocked(null);
             final long identity = Binder.clearCallingIdentity();
             try {
                 Settings.Secure.putStringForUser(mContext.getContentResolver(),
@@ -1967,7 +1947,7 @@
     }
 
     private boolean canRequestAndRequestsTouchExplorationLocked(
-            AccessibilityServiceConnection service, UserState userState) {
+            AccessibilityServiceConnection service, AccessibilityUserState userState) {
         // Service not ready or cannot request the feature - well nothing to do.
         if (!service.canReceiveEventsLocked() || !service.mRequestTouchExplorationMode) {
             return false;
@@ -1997,7 +1977,7 @@
         return false;
     }
 
-    private void updateMagnificationLocked(UserState userState) {
+    private void updateMagnificationLocked(AccessibilityUserState userState) {
         if (userState.mUserId != mCurrentUserId) {
             return;
         }
@@ -2012,8 +1992,8 @@
         // We would skip overlay display because it uses overlay window to simulate secondary
         // displays in one display. It's not a real display and there's no input events for it.
         final ArrayList<Display> displays = getValidDisplayList();
-        if (userState.mIsDisplayMagnificationEnabled
-                || userState.mIsNavBarMagnificationEnabled) {
+        if (userState.isDisplayMagnificationEnabledLocked()
+                || userState.isNavBarMagnificationEnabledLocked()) {
             for (int i = 0; i < displays.size(); i++) {
                 final Display display = displays.get(i);
                 getMagnificationController().register(display.getDisplayId());
@@ -2037,7 +2017,7 @@
      * Returns whether the specified user has any services that are capable of
      * controlling magnification.
      */
-    private boolean userHasMagnificationServicesLocked(UserState userState) {
+    private boolean userHasMagnificationServicesLocked(AccessibilityUserState userState) {
         final List<AccessibilityServiceConnection> services = userState.mBoundServices;
         for (int i = 0, count = services.size(); i < count; i++) {
             final AccessibilityServiceConnection service = services.get(i);
@@ -2052,7 +2032,7 @@
      * Returns whether the specified user has any services that are capable of
      * controlling magnification and are actively listening for magnification updates.
      */
-    private boolean userHasListeningMagnificationServicesLocked(UserState userState,
+    private boolean userHasListeningMagnificationServicesLocked(AccessibilityUserState userState,
             int displayId) {
         final List<AccessibilityServiceConnection> services = userState.mBoundServices;
         for (int i = 0, count = services.size(); i < count; i++) {
@@ -2065,7 +2045,7 @@
         return false;
     }
 
-    private void updateFingerprintGestureHandling(UserState userState) {
+    private void updateFingerprintGestureHandling(AccessibilityUserState userState) {
         final List<AccessibilityServiceConnection> services;
         synchronized (mLock) {
             services = userState.mBoundServices;
@@ -2097,7 +2077,7 @@
         }
     }
 
-    private void updateAccessibilityButtonTargetsLocked(UserState userState) {
+    private void updateAccessibilityButtonTargetsLocked(AccessibilityUserState userState) {
         for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
             final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
             if (service.mRequestAccessibilityButton) {
@@ -2107,9 +2087,9 @@
         }
     }
 
-    private void updateRecommendedUiTimeoutLocked(UserState userState) {
-        int newNonInteractiveUiTimeout = userState.mUserNonInteractiveUiTimeout;
-        int newInteractiveUiTimeout = userState.mUserInteractiveUiTimeout;
+    private void updateRecommendedUiTimeoutLocked(AccessibilityUserState userState) {
+        int newNonInteractiveUiTimeout = userState.getUserNonInteractiveUiTimeoutLocked();
+        int newInteractiveUiTimeout = userState.getUserInteractiveUiTimeoutLocked();
         // read from a11y services if user does not specify value
         if (newNonInteractiveUiTimeout == 0 || newInteractiveUiTimeout == 0) {
             int serviceNonInteractiveUiTimeout = 0;
@@ -2132,8 +2112,8 @@
                 newInteractiveUiTimeout = serviceInteractiveUiTimeout;
             }
         }
-        userState.mNonInteractiveUiTimeout = newNonInteractiveUiTimeout;
-        userState.mInteractiveUiTimeout = newInteractiveUiTimeout;
+        userState.setNonInteractiveUiTimeoutLocked(newNonInteractiveUiTimeout);
+        userState.setInteractiveUiTimeoutLocked(newInteractiveUiTimeout);
     }
 
     @GuardedBy("mLock")
@@ -2180,8 +2160,8 @@
         final Map<ComponentName, ToggleableFrameworkFeatureInfo> frameworkFeatureMap =
                 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
         synchronized(mLock) {
-            final UserState userState = getUserStateLocked(mCurrentUserId);
-            final ComponentName serviceName = userState.mServiceToEnableWithShortcut;
+            final AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
+            final ComponentName serviceName = userState.getServiceToEnableWithShortcutLocked();
             if (serviceName == null) {
                 return;
             }
@@ -2218,11 +2198,11 @@
                     "getAccessibilityShortcutService requires the MANAGE_ACCESSIBILITY permission");
         }
         synchronized(mLock) {
-            final UserState userState = getUserStateLocked(mCurrentUserId);
-            if (userState.mServiceToEnableWithShortcut == null) {
+            final AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
+            if (userState.getServiceToEnableWithShortcutLocked() == null) {
                 return null;
             }
-            return userState.mServiceToEnableWithShortcut.flattenToString();
+            return userState.getServiceToEnableWithShortcutLocked().flattenToString();
         }
     }
 
@@ -2237,7 +2217,7 @@
                         userId);
         setting.write(ComponentNameSet.add(setting.read(), componentName));
 
-        UserState userState = getUserStateLocked(userId);
+        AccessibilityUserState userState = getUserStateLocked(userId);
         if (userState.mEnabledServices.add(componentName)) {
             onUserStateChangedLocked(userState);
         }
@@ -2254,7 +2234,7 @@
                         userId);
         setting.write(ComponentNameSet.remove(setting.read(), componentName));
 
-        UserState userState = getUserStateLocked(userId);
+        AccessibilityUserState userState = getUserStateLocked(userId);
         if (userState.mEnabledServices.remove(componentName)) {
             onUserStateChangedLocked(userState);
         }
@@ -2322,14 +2302,14 @@
     @Override
     public long getRecommendedTimeoutMillis() {
         synchronized(mLock) {
-            final UserState userState = getCurrentUserStateLocked();
+            final AccessibilityUserState userState = getCurrentUserStateLocked();
             return getRecommendedTimeoutMillisLocked(userState);
         }
     }
 
-    private long getRecommendedTimeoutMillisLocked(UserState userState) {
-        return IntPair.of(userState.mInteractiveUiTimeout,
-                userState.mNonInteractiveUiTimeout);
+    private long getRecommendedTimeoutMillisLocked(AccessibilityUserState userState) {
+        return IntPair.of(userState.getInteractiveUiTimeoutLocked(),
+                userState.getNonInteractiveUiTimeoutLocked());
     }
 
     @Override
@@ -2338,78 +2318,20 @@
         synchronized (mLock) {
             pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)");
             pw.println();
+            pw.append("currentUserId=").append(String.valueOf(mCurrentUserId));
+            pw.println();
             final int userCount = mUserStates.size();
             for (int i = 0; i < userCount; i++) {
-                UserState userState = mUserStates.valueAt(i);
-                pw.append("User state[attributes:{id=" + userState.mUserId);
-                pw.append(", currentUser=" + (userState.mUserId == mCurrentUserId));
-                pw.append(", touchExplorationEnabled=" + userState.mIsTouchExplorationEnabled);
-                pw.append(", displayMagnificationEnabled="
-                        + userState.mIsDisplayMagnificationEnabled);
-                pw.append(", navBarMagnificationEnabled="
-                        + userState.mIsNavBarMagnificationEnabled);
-                pw.append(", autoclickEnabled=" + userState.mIsAutoclickEnabled);
-                pw.append(", nonInteractiveUiTimeout=" + userState.mNonInteractiveUiTimeout);
-                pw.append(", interactiveUiTimeout=" + userState.mInteractiveUiTimeout);
-                pw.append(", installedServiceCount=" + userState.mInstalledServices.size());
-                if (mUiAutomationManager.isUiAutomationRunningLocked()) {
-                    pw.append(", ");
-                    mUiAutomationManager.dumpUiAutomationService(fd, pw, args);
-                    pw.println();
-                }
-                pw.append("}");
-                pw.println();
-                pw.append("     Bound services:{");
-                final int serviceCount = userState.mBoundServices.size();
-                for (int j = 0; j < serviceCount; j++) {
-                    if (j > 0) {
-                        pw.append(", ");
-                        pw.println();
-                        pw.append("                     ");
-                    }
-                    AccessibilityServiceConnection service = userState.mBoundServices.get(j);
-                    service.dump(fd, pw, args);
-                }
-                pw.println("}");
-                pw.append("     Enabled services:{");
-                Iterator<ComponentName> it = userState.mEnabledServices.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("}");
-                pw.append("     Binding services:{");
-                it = userState.mBindingServices.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("}]");
+                mUserStates.valueAt(i).dump(fd, pw, args);
+            }
+            if (mUiAutomationManager.isUiAutomationRunningLocked()) {
+                mUiAutomationManager.dumpUiAutomationService(fd, pw, args);
                 pw.println();
             }
             mA11yWindowManager.dump(fd, pw, args);
         }
     }
 
-    private void putSecureIntForUser(String key, int value, int userid) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            Settings.Secure.putIntForUser(mContext.getContentResolver(), key, value, userid);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
     //TODO remove after refactoring KeyEventDispatcherTest
     final class MainHandler extends Handler {
         public static final int MSG_SEND_KEY_EVENT_TO_INPUT_FILTER = 8;
@@ -2446,7 +2368,7 @@
 
     @Override
     public void onClientChangeLocked(boolean serviceInfoChanged) {
-        AccessibilityManagerService.UserState userState = getUserStateLocked(mCurrentUserId);
+        AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
         onUserStateChangedLocked(userState);
         if (serviceInfoChanged) {
             scheduleNotifyClientsOfServicesStateChangeLocked(userState);
@@ -2474,7 +2396,7 @@
             info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
             info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
             info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-            final UserState userState;
+            final AccessibilityUserState userState;
             synchronized (mLock) {
                 userState = getCurrentUserStateLocked();
             }
@@ -2593,7 +2515,7 @@
                 if (mInputFilter != null) {
                     mInputFilter.onDisplayChanged();
                 }
-                UserState userState = getCurrentUserStateLocked();
+                AccessibilityUserState userState = getCurrentUserStateLocked();
                 if (displayId != Display.DEFAULT_DISPLAY) {
                     final List<AccessibilityServiceConnection> services = userState.mBoundServices;
                     for (int i = 0; i < services.size(); i++) {
@@ -2618,7 +2540,7 @@
                 if (mInputFilter != null) {
                     mInputFilter.onDisplayChanged();
                 }
-                UserState userState = getCurrentUserStateLocked();
+                AccessibilityUserState userState = getCurrentUserStateLocked();
                 if (displayId != Display.DEFAULT_DISPLAY) {
                     final List<AccessibilityServiceConnection> services = userState.mBoundServices;
                     for (int i = 0; i < services.size(); i++) {
@@ -2658,7 +2580,8 @@
         final String[] mPackageNames;
         int mLastSentRelevantEventTypes;
 
-        private Client(IAccessibilityManagerClient callback, int clientUid, UserState userState) {
+        private Client(IAccessibilityManagerClient callback, int clientUid,
+                AccessibilityUserState userState) {
             mCallback = callback;
             mPackageNames = mPackageManager.getPackagesForUid(clientUid);
             synchronized (mLock) {
@@ -2667,316 +2590,6 @@
         }
     }
 
-    public class UserState {
-        public final int mUserId;
-
-        // Non-transient state.
-
-        public final RemoteCallbackList<IAccessibilityManagerClient> mUserClients =
-                new RemoteCallbackList<>();
-
-        // Transient state.
-
-        public final ArrayList<AccessibilityServiceConnection> mBoundServices = new ArrayList<>();
-
-        public final Map<ComponentName, AccessibilityServiceConnection> mComponentNameToServiceMap =
-                new HashMap<>();
-
-        public final List<AccessibilityServiceInfo> mInstalledServices =
-                new ArrayList<>();
-
-        public final List<AccessibilityShortcutInfo> mInstalledShortcuts = new ArrayList<>();
-
-        private final Set<ComponentName> mBindingServices = new HashSet<>();
-
-        public final Set<ComponentName> mEnabledServices = new HashSet<>();
-
-        public final Set<ComponentName> mTouchExplorationGrantedServices =
-                new HashSet<>();
-
-        public ComponentName mServiceChangingSoftKeyboardMode;
-
-        public ComponentName mServiceToEnableWithShortcut;
-
-        public int mLastSentClientState = -1;
-        public int mNonInteractiveUiTimeout = 0;
-        public int mInteractiveUiTimeout = 0;
-
-        private int mSoftKeyboardShowMode = 0;
-
-        public boolean mIsNavBarMagnificationAssignedToAccessibilityButton;
-        public ComponentName mServiceAssignedToAccessibilityButton;
-
-        public boolean mIsTouchExplorationEnabled;
-        public boolean mIsTextHighContrastEnabled;
-        public boolean mIsDisplayMagnificationEnabled;
-        public boolean mIsNavBarMagnificationEnabled;
-        public boolean mIsAutoclickEnabled;
-        public boolean mIsPerformGesturesEnabled;
-        public boolean mIsFilterKeyEventsEnabled;
-        public int mUserNonInteractiveUiTimeout;
-        public int mUserInteractiveUiTimeout;
-
-        private boolean mBindInstantServiceAllowed;
-
-        public UserState(int userId) {
-            mUserId = userId;
-        }
-
-        public int getClientState() {
-            int clientState = 0;
-            final boolean a11yEnabled = (mUiAutomationManager.isUiAutomationRunningLocked()
-                    || isHandlingAccessibilityEvents());
-            if (a11yEnabled) {
-                clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
-            }
-            // Touch exploration relies on enabled accessibility.
-            if (a11yEnabled && mIsTouchExplorationEnabled) {
-                clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
-            }
-            if (mIsTextHighContrastEnabled) {
-                clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
-            }
-            return clientState;
-        }
-
-        public boolean isHandlingAccessibilityEvents() {
-            return !mBoundServices.isEmpty() || !mBindingServices.isEmpty();
-        }
-
-        public void onSwitchToAnotherUserLocked() {
-            // Unbind all services.
-            unbindAllServicesLocked(this);
-
-            // Clear service management state.
-            mBoundServices.clear();
-            mBindingServices.clear();
-
-            // Clear event management state.
-            mLastSentClientState = -1;
-
-            // clear UI timeout
-            mNonInteractiveUiTimeout = 0;
-            mInteractiveUiTimeout = 0;
-
-            // Clear state persisted in settings.
-            mEnabledServices.clear();
-            mTouchExplorationGrantedServices.clear();
-            mIsTouchExplorationEnabled = false;
-            mIsDisplayMagnificationEnabled = false;
-            mIsNavBarMagnificationEnabled = false;
-            mServiceAssignedToAccessibilityButton = null;
-            mIsNavBarMagnificationAssignedToAccessibilityButton = false;
-            mIsAutoclickEnabled = false;
-            mUserNonInteractiveUiTimeout = 0;
-            mUserInteractiveUiTimeout = 0;
-        }
-
-        public void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
-            if (!mBoundServices.contains(serviceConnection)) {
-                serviceConnection.onAdded();
-                mBoundServices.add(serviceConnection);
-                mComponentNameToServiceMap.put(serviceConnection.mComponentName, serviceConnection);
-                scheduleNotifyClientsOfServicesStateChangeLocked(this);
-            }
-        }
-
-        /**
-         * Removes a service.
-         * There are three states to a service here: off, bound, and binding.
-         * This stops tracking the service as bound.
-         *
-         * @param serviceConnection The service.
-         */
-        public void removeServiceLocked(AccessibilityServiceConnection serviceConnection) {
-            mBoundServices.remove(serviceConnection);
-            serviceConnection.onRemoved();
-            if ((mServiceChangingSoftKeyboardMode != null)
-                    && (mServiceChangingSoftKeyboardMode.equals(
-                            serviceConnection.getServiceInfo().getComponentName()))) {
-                setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
-            }
-            // It may be possible to bind a service twice, which confuses the map. Rebuild the map
-            // to make sure we can still reach a service
-            mComponentNameToServiceMap.clear();
-            for (int i = 0; i < mBoundServices.size(); i++) {
-                AccessibilityServiceConnection boundClient = mBoundServices.get(i);
-                mComponentNameToServiceMap.put(boundClient.mComponentName, boundClient);
-            }
-            scheduleNotifyClientsOfServicesStateChangeLocked(this);
-        }
-
-        /**
-         * Make sure a services disconnected but still 'on' state is reflected in UserState
-         * 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.
-         *
-         * @param serviceConnection The service.
-         */
-        public void serviceDisconnectedLocked(AccessibilityServiceConnection serviceConnection) {
-            removeServiceLocked(serviceConnection);
-            mBindingServices.add(serviceConnection.getComponentName());
-        }
-
-        public Set<ComponentName> getBindingServicesLocked() {
-            return mBindingServices;
-        }
-
-        /**
-         * Returns enabled service list.
-         */
-        public Set<ComponentName> getEnabledServicesLocked() {
-            return mEnabledServices;
-        }
-
-        public int getSoftKeyboardShowMode() {
-            return mSoftKeyboardShowMode;
-        }
-
-        /**
-         * Set the soft keyboard mode. This mode is a bit odd, as it spans multiple settings.
-         * The ACCESSIBILITY_SOFT_KEYBOARD_MODE setting can be checked by the rest of the system
-         * to see if it should suppress showing the IME. The SHOW_IME_WITH_HARD_KEYBOARD setting
-         * setting can be changed by the user, and prevents the system from suppressing the soft
-         * keyboard when the hard keyboard is connected. The hard keyboard setting needs to defer
-         * to the user's preference, if they have supplied one.
-         *
-         * @param newMode The new mode
-         * @param requester The service requesting the change, so we can undo it when the
-         *                  service stops. Set to null if something other than a service is forcing
-         *                  the change.
-         *
-         * @return Whether or not the soft keyboard mode equals the new mode after the call
-         */
-        public boolean setSoftKeyboardModeLocked(int newMode, @Nullable ComponentName requester) {
-            if ((newMode != SHOW_MODE_AUTO) && (newMode != SHOW_MODE_HIDDEN)
-                    && (newMode != SHOW_MODE_IGNORE_HARD_KEYBOARD))
-            {
-                Slog.w(LOG_TAG, "Invalid soft keyboard mode");
-                return false;
-            }
-            if (mSoftKeyboardShowMode == newMode) return true;
-
-            if (newMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
-                if (hasUserOverriddenHardKeyboardSettingLocked()) {
-                    // The user has specified a default for this setting
-                    return false;
-                }
-                // Save the original value. But don't do this if the value in settings is already
-                // the new mode. That happens when we start up after a reboot, and we don't want
-                // to overwrite the value we had from when we first started controlling the setting.
-                if (getSoftKeyboardValueFromSettings() != SHOW_MODE_IGNORE_HARD_KEYBOARD) {
-                    setOriginalHardKeyboardValue(
-                            Settings.Secure.getInt(mContext.getContentResolver(),
-                                    Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0);
-                }
-                putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1, mUserId);
-            } else if (mSoftKeyboardShowMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
-                putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
-                        getOriginalHardKeyboardValue() ? 1 : 0, mUserId);
-            }
-
-            saveSoftKeyboardValueToSettings(newMode);
-            mSoftKeyboardShowMode = newMode;
-            mServiceChangingSoftKeyboardMode = requester;
-            notifySoftKeyboardShowModeChangedLocked(mSoftKeyboardShowMode);
-            return true;
-        }
-
-        /**
-         * If the settings are inconsistent with the internal state, make the internal state
-         * match the settings.
-         */
-        public void reconcileSoftKeyboardModeWithSettingsLocked() {
-            final ContentResolver cr = mContext.getContentResolver();
-            final boolean showWithHardKeyboardSettings =
-                    Settings.Secure.getInt(cr, Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0;
-            if (mSoftKeyboardShowMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
-                if (!showWithHardKeyboardSettings) {
-                    // The user has overridden the setting. Respect that and prevent further changes
-                    // to this behavior.
-                    setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
-                    setUserOverridesHardKeyboardSettingLocked();
-                }
-            }
-
-            // If the setting and the internal state are out of sync, set both to default
-            if (getSoftKeyboardValueFromSettings() != mSoftKeyboardShowMode)
-            {
-                Slog.e(LOG_TAG,
-                        "Show IME setting inconsistent with internal state. Overwriting");
-                setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
-                putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
-                        SHOW_MODE_AUTO, mUserId);
-            }
-        }
-
-        private void setUserOverridesHardKeyboardSettingLocked() {
-            final int softKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0);
-            putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
-                    softKeyboardSetting | SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN,
-                    mUserId);
-        }
-
-        private boolean hasUserOverriddenHardKeyboardSettingLocked() {
-            final int softKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0);
-            return (softKeyboardSetting & SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN)
-                    != 0;
-        }
-
-        private void setOriginalHardKeyboardValue(boolean originalHardKeyboardValue) {
-            final int oldSoftKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0);
-            final int newSoftKeyboardSetting = oldSoftKeyboardSetting
-                    & (~SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE)
-                    | ((originalHardKeyboardValue) ? SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE : 0);
-            putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
-                    newSoftKeyboardSetting, mUserId);
-        }
-
-        private void saveSoftKeyboardValueToSettings(int softKeyboardShowMode) {
-            final int oldSoftKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0);
-            final int newSoftKeyboardSetting = oldSoftKeyboardSetting & (~SHOW_MODE_MASK)
-                    | softKeyboardShowMode;
-            putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
-                    newSoftKeyboardSetting, mUserId);
-        }
-
-        private int getSoftKeyboardValueFromSettings() {
-            return Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
-                    SHOW_MODE_AUTO) & SHOW_MODE_MASK;
-        }
-
-        private boolean getOriginalHardKeyboardValue() {
-            return (Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0)
-                    & SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE) != 0;
-        }
-
-        public boolean getBindInstantServiceAllowed() {
-            synchronized (mLock) {
-                return mBindInstantServiceAllowed;
-            }
-        }
-
-        public void setBindInstantServiceAllowed(boolean allowed) {
-            synchronized (mLock) {
-                mContext.enforceCallingOrSelfPermission(
-                        Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
-                        "setBindInstantServiceAllowed");
-                if (allowed) {
-                    mBindInstantServiceAllowed = allowed;
-                    onUserStateChangedLocked(this);
-                }
-            }
-        }
-    }
-
     private final class AccessibilityContentObserver extends ContentObserver {
 
         private final Uri mTouchExplorationEnabledUri = Settings.Secure.getUriFor(
@@ -3057,7 +2670,7 @@
             synchronized (mLock) {
                 // Profiles share the accessibility state of the parent. Therefore,
                 // we are checking for changes only the parent settings.
-                UserState userState = getCurrentUserStateLocked();
+                AccessibilityUserState userState = getCurrentUserStateLocked();
 
                 if (mTouchExplorationEnabledUri.equals(uri)) {
                     if (readTouchExplorationEnabledSettingLocked(userState)) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index e7f3ccc..d154060 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -35,7 +35,6 @@
 import android.util.Slog;
 import android.view.Display;
 
-import com.android.server.accessibility.AccessibilityManagerService.UserState;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.lang.ref.WeakReference;
@@ -52,13 +51,13 @@
 class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
     private static final String LOG_TAG = "AccessibilityServiceConnection";
     /*
-     Holding a weak reference so there isn't a loop of references. UserState keeps lists of bound
-     and binding services. These are freed on user changes, but just in case it somehow gets lost
-     the weak reference will let the memory get GCed.
+     Holding a weak reference so there isn't a loop of references. AccessibilityUserState keeps
+     lists of bound and binding services. These are freed on user changes, but just in case it
+     somehow gets lost the weak reference will let the memory get GCed.
 
      Having the reference be null when being called is a very bad sign, but we check the condition.
     */
-    final WeakReference<UserState> mUserStateWeakReference;
+    final WeakReference<AccessibilityUserState> mUserStateWeakReference;
     final Intent mIntent;
 
     private final Handler mMainHandler;
@@ -66,7 +65,7 @@
     private boolean mWasConnectedAndDied;
 
 
-    public AccessibilityServiceConnection(UserState userState, Context context,
+    AccessibilityServiceConnection(AccessibilityUserState userState, Context context,
             ComponentName componentName,
             AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
             Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport,
@@ -74,7 +73,7 @@
             SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager awm) {
         super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock,
                 securityPolicy, systemSupport, windowManagerInternal, systemActionPerfomer, awm);
-        mUserStateWeakReference = new WeakReference<UserState>(userState);
+        mUserStateWeakReference = new WeakReference<AccessibilityUserState>(userState);
         mIntent = new Intent().setComponent(mComponentName);
         mMainHandler = mainHandler;
         mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
@@ -89,13 +88,13 @@
     }
 
     public void bindLocked() {
-        UserState userState = mUserStateWeakReference.get();
+        AccessibilityUserState userState = mUserStateWeakReference.get();
         if (userState == null) return;
         final long identity = Binder.clearCallingIdentity();
         try {
             int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
                     | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS;
-            if (userState.getBindInstantServiceAllowed()) {
+            if (userState.getBindInstantServiceAllowedLocked()) {
                 flags |= Context.BIND_ALLOW_INSTANT;
             }
             if (mService == null && mContext.bindServiceAsUser(
@@ -109,7 +108,7 @@
 
     public void unbindLocked() {
         mContext.unbindService(this);
-        UserState userState = mUserStateWeakReference.get();
+        AccessibilityUserState userState = mUserStateWeakReference.get();
         if (userState == null) return;
         userState.removeServiceLocked(this);
         mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
@@ -123,7 +122,7 @@
     @Override
     public void disableSelf() {
         synchronized (mLock) {
-            UserState userState = mUserStateWeakReference.get();
+            AccessibilityUserState userState = mUserStateWeakReference.get();
             if (userState == null) return;
             if (userState.getEnabledServicesLocked().remove(mComponentName)) {
                 final long identity = Binder.clearCallingIdentity();
@@ -156,7 +155,7 @@
                 }
             }
             mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
-            UserState userState = mUserStateWeakReference.get();
+            AccessibilityUserState userState = mUserStateWeakReference.get();
             if (userState == null) return;
             userState.addServiceLocked(this);
             mSystemSupport.onClientChangeLocked(false);
@@ -177,7 +176,7 @@
     private void initializeService() {
         IAccessibilityServiceClient serviceInterface = null;
         synchronized (mLock) {
-            UserState userState = mUserStateWeakReference.get();
+            AccessibilityUserState userState = mUserStateWeakReference.get();
             if (userState == null) return;
             Set<ComponentName> bindingServices = userState.getBindingServicesLocked();
             if (bindingServices.contains(mComponentName) || mWasConnectedAndDied) {
@@ -240,7 +239,7 @@
             if (!hasRightsToCurrentUserLocked()) {
                 return false;
             }
-            final UserState userState = mUserStateWeakReference.get();
+            final AccessibilityUserState userState = mUserStateWeakReference.get();
             if (userState == null) return false;
             return userState.setSoftKeyboardModeLocked(showMode, mComponentName);
         }
@@ -248,8 +247,8 @@
 
     @Override
     public int getSoftKeyboardShowMode() {
-        final UserState userState = mUserStateWeakReference.get();
-        return (userState != null) ? userState.getSoftKeyboardShowMode() : 0;
+        final AccessibilityUserState userState = mUserStateWeakReference.get();
+        return (userState != null) ? userState.getSoftKeyboardShowModeLocked() : 0;
     }
 
     @Override
@@ -258,7 +257,7 @@
             if (!hasRightsToCurrentUserLocked()) {
                 return false;
             }
-            UserState userState = mUserStateWeakReference.get();
+            AccessibilityUserState userState = mUserStateWeakReference.get();
             return (userState != null) && isAccessibilityButtonAvailableLocked(userState);
         }
     }
@@ -273,7 +272,7 @@
                 return;
             }
             mWasConnectedAndDied = true;
-            UserState userState = mUserStateWeakReference.get();
+            AccessibilityUserState userState = mUserStateWeakReference.get();
             if (userState != null) {
                 userState.serviceDisconnectedLocked(this);
             }
@@ -283,7 +282,7 @@
         }
     }
 
-    public boolean isAccessibilityButtonAvailableLocked(UserState userState) {
+    public boolean isAccessibilityButtonAvailableLocked(AccessibilityUserState userState) {
         // If the service does not request the accessibility button, it isn't available
         if (!mRequestAccessibilityButton) {
             return false;
@@ -295,8 +294,8 @@
         }
 
         // If magnification is on and assigned to the accessibility button, services cannot be
-        if (userState.mIsNavBarMagnificationEnabled
-                && userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+        if (userState.isNavBarMagnificationEnabledLocked()
+                && userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
             return false;
         }
 
@@ -314,13 +313,14 @@
             return true;
         } else {
             // With more than one active service, we derive the target from the user's settings
-            if (userState.mServiceAssignedToAccessibilityButton == null) {
+            if (userState.getServiceAssignedToAccessibilityButtonLocked() == null) {
                 // If the user has not made an assignment, we treat the button as available to
                 // all services until the user interacts with the button to make an assignment
                 return true;
             } else {
                 // If an assignment was made, it defines availability
-                return mComponentName.equals(userState.mServiceAssignedToAccessibilityButton);
+                return mComponentName.equals(
+                        userState.getServiceAssignedToAccessibilityButtonLocked());
             }
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
new file mode 100644
index 0000000..69f1e0e
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -0,0 +1,573 @@
+/*
+ * 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.accessibility;
+
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK;
+
+import android.accessibilityservice.AccessibilityService.SoftKeyboardShowMode;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityShortcutInfo;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteCallbackList;
+import android.provider.Settings;
+import android.util.Slog;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Class that hold states and settings per user and share between
+ * {@link AccessibilityManagerService} and {@link AccessibilityServiceConnection}.
+ */
+class AccessibilityUserState {
+    private static final String LOG_TAG = AccessibilityUserState.class.getSimpleName();
+
+    final int mUserId;
+
+    // Non-transient state.
+
+    final RemoteCallbackList<IAccessibilityManagerClient> mUserClients = new RemoteCallbackList<>();
+
+    // Transient state.
+
+    final ArrayList<AccessibilityServiceConnection> mBoundServices = new ArrayList<>();
+
+    final Map<ComponentName, AccessibilityServiceConnection> mComponentNameToServiceMap =
+            new HashMap<>();
+
+    final List<AccessibilityServiceInfo> mInstalledServices = new ArrayList<>();
+
+    final List<AccessibilityShortcutInfo> mInstalledShortcuts = new ArrayList<>();
+
+    final Set<ComponentName> mBindingServices = new HashSet<>();
+
+    final Set<ComponentName> mEnabledServices = new HashSet<>();
+
+    final Set<ComponentName> mTouchExplorationGrantedServices = new HashSet<>();
+
+    private final ServiceInfoChangeListener mServiceInfoChangeListener;
+
+    private ComponentName mServiceAssignedToAccessibilityButton;
+
+    private ComponentName mServiceChangingSoftKeyboardMode;
+
+    private ComponentName mServiceToEnableWithShortcut;
+
+    private boolean mBindInstantServiceAllowed;
+    private boolean mIsAutoclickEnabled;
+    private boolean mIsDisplayMagnificationEnabled;
+    private boolean mIsFilterKeyEventsEnabled;
+    private boolean mIsNavBarMagnificationAssignedToAccessibilityButton;
+    private boolean mIsNavBarMagnificationEnabled;
+    private boolean mIsPerformGesturesEnabled;
+    private boolean mIsTextHighContrastEnabled;
+    private boolean mIsTouchExplorationEnabled;
+    private int mUserInteractiveUiTimeout;
+    private int mUserNonInteractiveUiTimeout;
+    private int mNonInteractiveUiTimeout = 0;
+    private int mInteractiveUiTimeout = 0;
+    private int mLastSentClientState = -1;
+
+    private Context mContext;
+
+    @SoftKeyboardShowMode
+    private int mSoftKeyboardShowMode = SHOW_MODE_AUTO;
+
+    interface ServiceInfoChangeListener {
+        void onServiceInfoChangedLocked(AccessibilityUserState userState);
+    }
+
+    AccessibilityUserState(int userId, @NonNull Context context,
+            @NonNull ServiceInfoChangeListener serviceInfoChangeListener) {
+        mUserId = userId;
+        mContext = context;
+        mServiceInfoChangeListener = serviceInfoChangeListener;
+    }
+
+    boolean isHandlingAccessibilityEventsLocked() {
+        return !mBoundServices.isEmpty() || !mBindingServices.isEmpty();
+    }
+
+    void onSwitchToAnotherUserLocked() {
+        // Unbind all services.
+        unbindAllServicesLocked();
+
+        // Clear service management state.
+        mBoundServices.clear();
+        mBindingServices.clear();
+
+        // Clear event management state.
+        mLastSentClientState = -1;
+
+        // clear UI timeout
+        mNonInteractiveUiTimeout = 0;
+        mInteractiveUiTimeout = 0;
+
+        // Clear state persisted in settings.
+        mEnabledServices.clear();
+        mTouchExplorationGrantedServices.clear();
+        mIsTouchExplorationEnabled = false;
+        mIsDisplayMagnificationEnabled = false;
+        mIsNavBarMagnificationEnabled = false;
+        mServiceAssignedToAccessibilityButton = null;
+        mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+        mIsAutoclickEnabled = false;
+        mUserNonInteractiveUiTimeout = 0;
+        mUserInteractiveUiTimeout = 0;
+    }
+
+    void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
+        if (!mBoundServices.contains(serviceConnection)) {
+            serviceConnection.onAdded();
+            mBoundServices.add(serviceConnection);
+            mComponentNameToServiceMap.put(serviceConnection.getComponentName(), serviceConnection);
+            mServiceInfoChangeListener.onServiceInfoChangedLocked(this);
+        }
+    }
+
+    /**
+     * Removes a service.
+     * There are three states to a service here: off, bound, and binding.
+     * This stops tracking the service as bound.
+     *
+     * @param serviceConnection The service.
+     */
+    void removeServiceLocked(AccessibilityServiceConnection serviceConnection) {
+        mBoundServices.remove(serviceConnection);
+        serviceConnection.onRemoved();
+        if ((mServiceChangingSoftKeyboardMode != null)
+                && (mServiceChangingSoftKeyboardMode.equals(
+                serviceConnection.getServiceInfo().getComponentName()))) {
+            setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
+        }
+        // It may be possible to bind a service twice, which confuses the map. Rebuild the map
+        // to make sure we can still reach a service
+        mComponentNameToServiceMap.clear();
+        for (int i = 0; i < mBoundServices.size(); i++) {
+            AccessibilityServiceConnection boundClient = mBoundServices.get(i);
+            mComponentNameToServiceMap.put(boundClient.getComponentName(), boundClient);
+        }
+        mServiceInfoChangeListener.onServiceInfoChangedLocked(this);
+    }
+
+    /**
+     * 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.
+     *
+     * @param serviceConnection The service.
+     */
+    void serviceDisconnectedLocked(AccessibilityServiceConnection serviceConnection) {
+        removeServiceLocked(serviceConnection);
+        mBindingServices.add(serviceConnection.getComponentName());
+    }
+
+    /**
+     * Set the soft keyboard mode. This mode is a bit odd, as it spans multiple settings.
+     * The ACCESSIBILITY_SOFT_KEYBOARD_MODE setting can be checked by the rest of the system
+     * to see if it should suppress showing the IME. The SHOW_IME_WITH_HARD_KEYBOARD setting
+     * setting can be changed by the user, and prevents the system from suppressing the soft
+     * keyboard when the hard keyboard is connected. The hard keyboard setting needs to defer
+     * to the user's preference, if they have supplied one.
+     *
+     * @param newMode The new mode
+     * @param requester The service requesting the change, so we can undo it when the
+     *                  service stops. Set to null if something other than a service is forcing
+     *                  the change.
+     *
+     * @return Whether or not the soft keyboard mode equals the new mode after the call
+     */
+    boolean setSoftKeyboardModeLocked(@SoftKeyboardShowMode int newMode,
+            @Nullable ComponentName requester) {
+        if ((newMode != SHOW_MODE_AUTO)
+                && (newMode != SHOW_MODE_HIDDEN)
+                && (newMode != SHOW_MODE_IGNORE_HARD_KEYBOARD)) {
+            Slog.w(LOG_TAG, "Invalid soft keyboard mode");
+            return false;
+        }
+        if (mSoftKeyboardShowMode == newMode) {
+            return true;
+        }
+
+        if (newMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
+            if (hasUserOverriddenHardKeyboardSetting()) {
+                // The user has specified a default for this setting
+                return false;
+            }
+            // Save the original value. But don't do this if the value in settings is already
+            // the new mode. That happens when we start up after a reboot, and we don't want
+            // to overwrite the value we had from when we first started controlling the setting.
+            if (getSoftKeyboardValueFromSettings() != SHOW_MODE_IGNORE_HARD_KEYBOARD) {
+                setOriginalHardKeyboardValue(getSecureIntForUser(
+                        Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0, mUserId) != 0);
+            }
+            putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1, mUserId);
+        } else if (mSoftKeyboardShowMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
+            putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+                    getOriginalHardKeyboardValue() ? 1 : 0, mUserId);
+        }
+
+        saveSoftKeyboardValueToSettings(newMode);
+        mSoftKeyboardShowMode = newMode;
+        mServiceChangingSoftKeyboardMode = requester;
+        for (int i = mBoundServices.size() - 1; i >= 0; i--) {
+            final AccessibilityServiceConnection service = mBoundServices.get(i);
+            service.notifySoftKeyboardShowModeChangedLocked(mSoftKeyboardShowMode);
+        }
+        return true;
+    }
+
+    @SoftKeyboardShowMode
+    int getSoftKeyboardShowModeLocked() {
+        return mSoftKeyboardShowMode;
+    }
+
+    /**
+     * If the settings are inconsistent with the internal state, make the internal state
+     * match the settings.
+     */
+    void reconcileSoftKeyboardModeWithSettingsLocked() {
+        final boolean showWithHardKeyboardSettings =
+                getSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0, mUserId) != 0;
+        if (mSoftKeyboardShowMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
+            if (!showWithHardKeyboardSettings) {
+                // The user has overridden the setting. Respect that and prevent further changes
+                // to this behavior.
+                setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
+                setUserOverridesHardKeyboardSetting();
+            }
+        }
+
+        // If the setting and the internal state are out of sync, set both to default
+        if (getSoftKeyboardValueFromSettings() != mSoftKeyboardShowMode) {
+            Slog.e(LOG_TAG, "Show IME setting inconsistent with internal state. Overwriting");
+            setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
+            putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+                    SHOW_MODE_AUTO, mUserId);
+        }
+    }
+
+    boolean getBindInstantServiceAllowedLocked() {
+        return mBindInstantServiceAllowed;
+    }
+
+    /* Need to have a permission check on callee */
+    void setBindInstantServiceAllowedLocked(boolean allowed) {
+        mBindInstantServiceAllowed = allowed;
+    }
+
+    Set<ComponentName> getBindingServicesLocked() {
+        return mBindingServices;
+    }
+
+    /**
+     * Returns enabled service list.
+     */
+    Set<ComponentName> getEnabledServicesLocked() {
+        return mEnabledServices;
+    }
+
+    List<AccessibilityServiceConnection> getBoundServicesLocked() {
+        return mBoundServices;
+    }
+
+    int getClientStateLocked(boolean isUiAutomationRunning) {
+        int clientState = 0;
+        final boolean a11yEnabled = isUiAutomationRunning
+                || isHandlingAccessibilityEventsLocked();
+        if (a11yEnabled) {
+            clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
+        }
+        // Touch exploration relies on enabled accessibility.
+        if (a11yEnabled && mIsTouchExplorationEnabled) {
+            clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
+        }
+        if (mIsTextHighContrastEnabled) {
+            clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
+        }
+        return clientState;
+    }
+
+    private void setUserOverridesHardKeyboardSetting() {
+        final int softKeyboardSetting = getSecureIntForUser(
+                Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId);
+        putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+                softKeyboardSetting | SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN,
+                mUserId);
+    }
+
+    private boolean hasUserOverriddenHardKeyboardSetting() {
+        final int softKeyboardSetting = getSecureIntForUser(
+                Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId);
+        return (softKeyboardSetting & SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN)
+                != 0;
+    }
+
+    private void setOriginalHardKeyboardValue(boolean originalHardKeyboardValue) {
+        final int oldSoftKeyboardSetting = getSecureIntForUser(
+                Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId);
+        final int newSoftKeyboardSetting = oldSoftKeyboardSetting
+                & (~SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE)
+                | ((originalHardKeyboardValue) ? SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE : 0);
+        putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+                newSoftKeyboardSetting, mUserId);
+    }
+
+    private void saveSoftKeyboardValueToSettings(int softKeyboardShowMode) {
+        final int oldSoftKeyboardSetting = getSecureIntForUser(
+                Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId);
+        final int newSoftKeyboardSetting = oldSoftKeyboardSetting & (~SHOW_MODE_MASK)
+                | softKeyboardShowMode;
+        putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+                newSoftKeyboardSetting, mUserId);
+    }
+
+    private int getSoftKeyboardValueFromSettings() {
+        return getSecureIntForUser(
+                Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId)
+                & SHOW_MODE_MASK;
+    }
+
+    private boolean getOriginalHardKeyboardValue() {
+        return (getSecureIntForUser(
+                Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId)
+                & SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE) != 0;
+    }
+
+    private void unbindAllServicesLocked() {
+        final List<AccessibilityServiceConnection> services = mBoundServices;
+        for (int count = services.size(); count > 0; count--) {
+            // When the service is unbound, it disappears from the list, so there's no need to
+            // keep track of the index
+            services.get(0).unbindLocked();
+        }
+    }
+
+    private int getSecureIntForUser(String key, int def, int userId) {
+        return Settings.Secure.getIntForUser(mContext.getContentResolver(), key, def, userId);
+    }
+
+    private void putSecureIntForUser(String key, int value, int userId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Settings.Secure.putIntForUser(mContext.getContentResolver(), key, value, userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.append("User state[");
+        pw.println();
+        pw.append("     attributes:{id=").append(String.valueOf(mUserId));
+        pw.append(", touchExplorationEnabled=").append(String.valueOf(mIsTouchExplorationEnabled));
+        pw.append(", displayMagnificationEnabled=").append(String.valueOf(
+                mIsDisplayMagnificationEnabled));
+        pw.append(", navBarMagnificationEnabled=").append(String.valueOf(
+                mIsNavBarMagnificationEnabled));
+        pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
+        pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout));
+        pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout));
+        pw.append(", installedServiceCount=").append(String.valueOf(mInstalledServices.size()));
+        pw.append("}");
+        pw.println();
+        pw.append("     Bound services:{");
+        final int serviceCount = mBoundServices.size();
+        for (int j = 0; j < serviceCount; j++) {
+            if (j > 0) {
+                pw.append(", ");
+                pw.println();
+                pw.append("                     ");
+            }
+            AccessibilityServiceConnection service = mBoundServices.get(j);
+            service.dump(fd, pw, args);
+        }
+        pw.println("}");
+        pw.append("     Enabled services:{");
+        Iterator<ComponentName> it = mEnabledServices.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("}");
+        pw.append("     Binding services:{");
+        it = mBindingServices.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("}]");
+    }
+
+    public boolean isAutoclickEnabledLocked() {
+        return mIsAutoclickEnabled;
+    }
+
+    public void setAutoclickEnabledLocked(boolean enabled) {
+        mIsAutoclickEnabled = enabled;
+    }
+
+    public boolean isDisplayMagnificationEnabledLocked() {
+        return mIsDisplayMagnificationEnabled;
+    }
+
+    public void setDisplayMagnificationEnabledLocked(boolean enabled) {
+        mIsDisplayMagnificationEnabled = enabled;
+    }
+
+    public boolean isFilterKeyEventsEnabledLocked() {
+        return mIsFilterKeyEventsEnabled;
+    }
+
+    public void setFilterKeyEventsEnabledLocked(boolean enabled) {
+        mIsFilterKeyEventsEnabled = enabled;
+    }
+
+    public int getInteractiveUiTimeoutLocked() {
+        return mInteractiveUiTimeout;
+    }
+
+    public void setInteractiveUiTimeoutLocked(int timeout) {
+        mInteractiveUiTimeout = timeout;
+    }
+
+    public int getLastSentClientStateLocked() {
+        return mLastSentClientState;
+    }
+
+    public void setLastSentClientStateLocked(int state) {
+        mLastSentClientState = state;
+    }
+
+    public boolean isNavBarMagnificationAssignedToAccessibilityButtonLocked() {
+        return mIsNavBarMagnificationAssignedToAccessibilityButton;
+    }
+
+    public void setNavBarMagnificationAssignedToAccessibilityButtonLocked(boolean assigned) {
+        mIsNavBarMagnificationAssignedToAccessibilityButton = assigned;
+    }
+
+    public boolean isNavBarMagnificationEnabledLocked() {
+        return mIsNavBarMagnificationEnabled;
+    }
+
+    public void setNavBarMagnificationEnabledLocked(boolean enabled) {
+        mIsNavBarMagnificationEnabled = enabled;
+    }
+
+    public int getNonInteractiveUiTimeoutLocked() {
+        return mNonInteractiveUiTimeout;
+    }
+
+    public void setNonInteractiveUiTimeoutLocked(int timeout) {
+        mNonInteractiveUiTimeout = timeout;
+    }
+
+    public boolean isPerformGesturesEnabledLocked() {
+        return mIsPerformGesturesEnabled;
+    }
+
+    public void setPerformGesturesEnabledLocked(boolean enabled) {
+        mIsPerformGesturesEnabled = enabled;
+    }
+
+    public ComponentName getServiceAssignedToAccessibilityButtonLocked() {
+        return mServiceAssignedToAccessibilityButton;
+    }
+
+    public void setServiceAssignedToAccessibilityButtonLocked(ComponentName componentName) {
+        mServiceAssignedToAccessibilityButton = componentName;
+    }
+
+    public ComponentName getServiceChangingSoftKeyboardModeLocked() {
+        return mServiceChangingSoftKeyboardMode;
+    }
+
+    public void setServiceChangingSoftKeyboardModeLocked(
+            ComponentName serviceChangingSoftKeyboardMode) {
+        mServiceChangingSoftKeyboardMode = serviceChangingSoftKeyboardMode;
+    }
+
+    public ComponentName getServiceToEnableWithShortcutLocked() {
+        return mServiceToEnableWithShortcut;
+    }
+
+    public void setServiceToEnableWithShortcutLocked(ComponentName componentName) {
+        mServiceToEnableWithShortcut = componentName;
+    }
+
+    public boolean isTextHighContrastEnabledLocked() {
+        return mIsTextHighContrastEnabled;
+    }
+
+    public void setTextHighContrastEnabledLocked(boolean enabled) {
+        mIsTextHighContrastEnabled = enabled;
+    }
+
+    public boolean isTouchExplorationEnabledLocked() {
+        return mIsTouchExplorationEnabled;
+    }
+
+    public void setTouchExplorationEnabledLocked(boolean enabled) {
+        mIsTouchExplorationEnabled = enabled;
+    }
+
+    public int getUserInteractiveUiTimeoutLocked() {
+        return mUserInteractiveUiTimeout;
+    }
+
+    public void setUserInteractiveUiTimeoutLocked(int timeout) {
+        mUserInteractiveUiTimeout = timeout;
+    }
+
+    public int getUserNonInteractiveUiTimeoutLocked() {
+        return mUserNonInteractiveUiTimeout;
+    }
+
+    public void setUserNonInteractiveUiTimeoutLocked(int timeout) {
+        mUserNonInteractiveUiTimeout = timeout;
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index b35300c..7d129ea 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -286,7 +286,7 @@
         window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
                 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
-        window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS);
+        window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
         window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
         window.setGravity(Gravity.BOTTOM | Gravity.CENTER);
         window.setCloseOnTouchOutside(true);
diff --git a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
index de48f4b..30ce4cf 100644
--- a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
@@ -93,8 +93,7 @@
         mTransportWhitelist = Preconditions.checkNotNull(whitelist);
         mCurrentTransportName = selectedTransport;
         mTransportStats = new TransportStats();
-        mTransportClientManager = TransportClientManager.createEncryptingClientManager(mUserId,
-                context, mTransportStats);
+        mTransportClientManager = new TransportClientManager(mUserId, context, mTransportStats);
     }
 
     @VisibleForTesting
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index e5e11ea..ac006df 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -239,7 +239,6 @@
     private final KeyValueBackupReporter mReporter;
     private final OnTaskFinishedListener mTaskFinishedListener;
     private final boolean mUserInitiated;
-    private final boolean mNonIncremental;
     private final int mCurrentOpToken;
     private final int mUserId;
     private final File mStateDirectory;
@@ -264,6 +263,7 @@
     // and at least one of the packages had data. Used to avoid updating current token for
     // empty backups.
     private boolean mHasDataToBackup;
+    private boolean mNonIncremental;
 
     /**
      * This {@link ConditionVariable} is used to signal that the cancel operation has been
@@ -412,6 +412,11 @@
         try {
             IBackupTransport transport = mTransportClient.connectOrThrow("KVBT.startTask()");
             String transportName = transport.name();
+            if (transportName.contains("EncryptedLocalTransport")) {
+                // Temporary code for EiTF POC. Only supports non-incremental backups.
+                mNonIncremental = true;
+            }
+
             mReporter.onTransportReady(transportName);
 
             // If we haven't stored PM metadata yet, we must initialize the transport.
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 1d666ad..dc24cff 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -59,6 +59,8 @@
     public static final int PACKAGE_CONFIGURATOR = 9;
     public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 10;
     public static final int PACKAGE_APP_PREDICTOR = 11;
+    public static final int PACKAGE_TELEPHONY = 12;
+    public static final int PACKAGE_WIFI = 13;
     @IntDef(value = {
         PACKAGE_SYSTEM,
         PACKAGE_SETUP_WIZARD,
@@ -72,6 +74,8 @@
         PACKAGE_CONFIGURATOR,
         PACKAGE_INCIDENT_REPORT_APPROVER,
         PACKAGE_APP_PREDICTOR,
+        PACKAGE_TELEPHONY,
+        PACKAGE_WIFI,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface KnownPackage {}
@@ -546,10 +550,11 @@
      */
     public abstract boolean isResolveActivityComponent(@NonNull ComponentInfo component);
 
+
     /**
-     * Returns the package name for a known package.
+     * Returns a list of package names for a known package
      */
-    public abstract @Nullable String getKnownPackageName(
+    public abstract @NonNull String[] getKnownPackageNames(
             @KnownPackage int knownPackage, int userId);
 
     /**
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index e0f60b4..0bb72cb 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1149,7 +1149,6 @@
     private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
         final NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addCapability(NET_CAPABILITY_INTERNET);
-        netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
         netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
         netCap.setSingleUid(uid);
         return netCap;
@@ -1159,7 +1158,6 @@
             int transportType, NetworkRequest.Type type) {
         final NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addCapability(NET_CAPABILITY_INTERNET);
-        netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
         if (transportType > -1) {
             netCap.addTransportType(transportType);
         }
diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
index b6e619e..44a8234 100644
--- a/services/core/java/com/android/server/GnssManagerService.java
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.Context;
@@ -298,7 +299,8 @@
      * @param packageName name of requesting package
      * @return true if callback is successfully added, false otherwise
      */
-    public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName) {
+    public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
+            @NonNull String listenerIdentity) {
         mContext.enforceCallingPermission(
                 android.Manifest.permission.LOCATION_HARDWARE,
                 "Location Hardware permission not granted to access hardware batching");
@@ -316,7 +318,8 @@
         }
 
         CallerIdentity callerIdentity =
-                new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
+                new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
+                        listenerIdentity);
         synchronized (mGnssBatchingLock) {
             mGnssBatchingCallback = callback;
             mGnssBatchingDeathCallback =
@@ -494,7 +497,7 @@
     private <TListener extends IInterface> boolean addGnssDataListenerLocked(
             TListener listener,
             String packageName,
-            String listenerName,
+            @NonNull String listenerIdentifier,
             RemoteListenerHelper<TListener> gnssDataProvider,
             ArrayMap<IBinder,
                     LinkedListener<TListener>> gnssDataListeners,
@@ -513,10 +516,11 @@
         }
 
         CallerIdentity callerIdentity =
-                new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
+                new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
+                        listenerIdentifier);
         LinkedListener<TListener> linkedListener =
                 new LocationManagerServiceUtils.LinkedListener<>(
-                        listener, listenerName, callerIdentity, binderDeathCallback);
+                        listener, listenerIdentifier, callerIdentity, binderDeathCallback);
         IBinder binder = listener.asBinder();
         if (!linkedListener.linkToListenerDeathNotificationLocked(binder)) {
             return false;
@@ -606,7 +610,7 @@
             return addGnssDataListenerLocked(
                     listener,
                     packageName,
-                    "GnssStatusListener",
+                    "Gnss status",
                     mGnssStatusProvider,
                     mGnssStatusListeners,
                     this::unregisterGnssStatusCallback);
@@ -632,12 +636,13 @@
      * @return true if listener is successfully added, false otherwise
      */
     public boolean addGnssMeasurementsListener(
-            IGnssMeasurementsListener listener, String packageName) {
+            IGnssMeasurementsListener listener, String packageName,
+            @NonNull String listenerIdentifier) {
         synchronized (mGnssMeasurementsListeners) {
             return addGnssDataListenerLocked(
                     listener,
                     packageName,
-                    "GnssMeasurementsListener",
+                    listenerIdentifier,
                     mGnssMeasurementsProvider,
                     mGnssMeasurementsListeners,
                     this::removeGnssMeasurementsListener);
@@ -689,12 +694,13 @@
      * @return true if listener is successfully added, false otherwise
      */
     public boolean addGnssNavigationMessageListener(
-            IGnssNavigationMessageListener listener, String packageName) {
+            IGnssNavigationMessageListener listener, String packageName,
+            @NonNull String listenerIdentifier) {
         synchronized (mGnssNavigationMessageListeners) {
             return addGnssDataListenerLocked(
                     listener,
                     packageName,
-                    "GnssNavigationMessageListener",
+                    listenerIdentifier,
                     mGnssNavigationMessageProvider,
                     mGnssNavigationMessageListeners,
                     this::removeGnssNavigationMessageListener);
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 09f62ff..aa22feb 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1223,8 +1223,10 @@
         PowerManager.WakeLock mWakeLock;
 
         private Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid,
-                String packageName, WorkSource workSource, boolean hideFromAppOps) {
-            super(new CallerIdentity(uid, pid, packageName), "LocationListener");
+                String packageName, WorkSource workSource, boolean hideFromAppOps,
+                @NonNull String listenerIdentifier) {
+            super(new CallerIdentity(uid, pid, packageName, listenerIdentifier),
+                    "LocationListener");
             mListener = listener;
             mPendingIntent = intent;
             if (listener != null) {
@@ -1532,9 +1534,12 @@
     }
 
     @Override
-    public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName) {
+    public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
+            String listenerIdentifier) {
+        Preconditions.checkNotNull(listenerIdentifier);
+
         return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback(
-                callback, packageName);
+                callback, packageName, listenerIdentifier);
     }
 
     @Override
@@ -1696,11 +1701,12 @@
         }
     }
 
-    private boolean reportLocationAccessNoThrow(
-            int pid, int uid, String packageName, int allowedResolutionLevel) {
+    private boolean reportLocationAccessNoThrow(int pid, int uid, String packageName,
+            int allowedResolutionLevel, @Nullable String message) {
         int op = resolutionLevelToOp(allowedResolutionLevel);
         if (op >= 0) {
-            if (mAppOps.noteOpNoThrow(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
+            if (mAppOps.noteOpNoThrow(op, uid, packageName, message)
+                    != AppOpsManager.MODE_ALLOWED) {
                 return false;
             }
         }
@@ -2133,12 +2139,13 @@
 
     @GuardedBy("mLock")
     private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
-            String packageName, WorkSource workSource, boolean hideFromAppOps) {
+            String packageName, WorkSource workSource, boolean hideFromAppOps,
+            @NonNull String listenerIdentifier) {
         IBinder binder = listener.asBinder();
         Receiver receiver = mReceivers.get(binder);
         if (receiver == null) {
             receiver = new Receiver(listener, null, pid, uid, packageName, workSource,
-                    hideFromAppOps);
+                    hideFromAppOps, listenerIdentifier);
             if (!receiver.linkToListenerDeathNotificationLocked(
                     receiver.getListener().asBinder())) {
                 return null;
@@ -2150,11 +2157,11 @@
 
     @GuardedBy("mLock")
     private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
-            WorkSource workSource, boolean hideFromAppOps) {
+            WorkSource workSource, boolean hideFromAppOps, @NonNull String listenerIdentifier) {
         Receiver receiver = mReceivers.get(intent);
         if (receiver == null) {
             receiver = new Receiver(null, intent, pid, uid, packageName, workSource,
-                    hideFromAppOps);
+                    hideFromAppOps, listenerIdentifier);
             mReceivers.put(intent, receiver);
         }
         return receiver;
@@ -2216,7 +2223,9 @@
 
     @Override
     public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
-            PendingIntent intent, String packageName) {
+            PendingIntent intent, String packageName, String listenerIdentifier) {
+        Preconditions.checkNotNull(listenerIdentifier);
+
         synchronized (mLock) {
             if (request == null) request = DEFAULT_LOCATION_REQUEST;
             checkPackageName(packageName);
@@ -2271,10 +2280,10 @@
                 Receiver receiver;
                 if (intent != null) {
                     receiver = getReceiverLocked(intent, pid, uid, packageName, workSource,
-                            hideFromAppOps);
+                            hideFromAppOps, listenerIdentifier);
                 } else {
                     receiver = getReceiverLocked(listener, pid, uid, packageName, workSource,
-                            hideFromAppOps);
+                            hideFromAppOps, listenerIdentifier);
                 }
                 requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName);
             } finally {
@@ -2343,9 +2352,9 @@
         synchronized (mLock) {
             Receiver receiver;
             if (intent != null) {
-                receiver = getReceiverLocked(intent, pid, uid, packageName, null, false);
+                receiver = getReceiverLocked(intent, pid, uid, packageName, null, false, "");
             } else {
-                receiver = getReceiverLocked(listener, pid, uid, packageName, null, false);
+                receiver = getReceiverLocked(listener, pid, uid, packageName, null, false, "");
             }
 
             long identity = Binder.clearCallingIdentity();
@@ -2464,8 +2473,8 @@
                 }
                 // Don't report location access if there is no last location to deliver.
                 if (lastLocation != null) {
-                    if (!reportLocationAccessNoThrow(
-                            pid, uid, packageName, allowedResolutionLevel)) {
+                    if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel,
+                            null)) {
                         if (D) {
                             Log.d(TAG, "not returning last loc for no op app: " + packageName);
                         }
@@ -2519,7 +2528,9 @@
 
     @Override
     public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent,
-            String packageName) {
+            String packageName, String listenerIdentifier) {
+        Preconditions.checkNotNull(listenerIdentifier);
+
         if (request == null) request = DEFAULT_LOCATION_REQUEST;
         int allowedResolutionLevel = getCallerAllowedResolutionLevel();
         checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel);
@@ -2564,9 +2575,8 @@
                         mActivityManager.getPackageImportance(packageName));
             }
 
-            mGeofenceManager.addFence(sanitizedRequest, geofence, intent,
-                    allowedResolutionLevel,
-                    uid, packageName);
+            mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
+                    uid, packageName, listenerIdentifier);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -2613,10 +2623,13 @@
     }
 
     @Override
-    public boolean addGnssMeasurementsListener(
-            IGnssMeasurementsListener listener, String packageName) {
+    public boolean addGnssMeasurementsListener(IGnssMeasurementsListener listener,
+            String packageName, String listenerIdentifier) {
+        Preconditions.checkNotNull(listenerIdentifier);
+
         return mGnssManagerService == null ? false
-                : mGnssManagerService.addGnssMeasurementsListener(listener, packageName);
+                : mGnssManagerService.addGnssMeasurementsListener(listener, packageName,
+                        listenerIdentifier);
     }
 
     @Override
@@ -2643,10 +2656,13 @@
     }
 
     @Override
-    public boolean addGnssNavigationMessageListener(
-            IGnssNavigationMessageListener listener, String packageName) {
+    public boolean addGnssNavigationMessageListener(IGnssNavigationMessageListener listener,
+            String packageName, String listenerIdentifier) {
+        Preconditions.checkNotNull(listenerIdentifier);
+
         return mGnssManagerService == null ? false
-                : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName);
+                : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName,
+                        listenerIdentifier);
     }
 
     @Override
@@ -2719,12 +2735,21 @@
                     return true;
                 }
             }
-
             return false;
         }
     }
 
     @Override
+    public List<String> getProviderPackages(String providerName) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
+                Manifest.permission.READ_DEVICE_CONFIG + " permission required");
+        synchronized (mLock) {
+            LocationProvider provider = getLocationProviderLocked(providerName);
+            return provider == null ? Collections.emptyList() : provider.getPackagesLocked();
+        }
+    }
+
+    @Override
     public void setExtraLocationControllerPackage(String packageName) {
         mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
                 Manifest.permission.LOCATION_HARDWARE + " permission required");
@@ -2943,7 +2968,8 @@
                             receiver.mCallerIdentity.mPid,
                             receiver.mCallerIdentity.mUid,
                             receiver.mCallerIdentity.mPackageName,
-                            receiver.mAllowedResolutionLevel)) {
+                            receiver.mAllowedResolutionLevel,
+                            "Location sent to " + receiver.mCallerIdentity.mListenerIdentifier)) {
                         if (D) {
                             Log.d(TAG, "skipping loc update for no op app: "
                                     + receiver.mCallerIdentity.mPackageName);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 0f8a3b5..447ed59 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -385,7 +385,7 @@
         mContext = context;
         mBatteryStats = BatteryStatsService.getService();
 
-        int numPhones = TelephonyManager.getDefault().getMaxPhoneCount();
+        int numPhones = TelephonyManager.getDefault().getSupportedModemCount();
         if (DBG) log("TelephonyRegistry: ctor numPhones=" + numPhones);
         mNumPhones = numPhones;
         mCallState = new int[numPhones];
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8c672a1..55cd933 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -900,7 +900,7 @@
 
         // The other observer methods are unused
         @Override
-        public void onIntentStarted(Intent intent) {
+        public void onIntentStarted(Intent intent, long timestampNs) {
         }
 
         @Override
@@ -912,7 +912,11 @@
         }
 
         @Override
-        public void onActivityLaunchFinished(byte[] finalActivity) {
+        public void onActivityLaunchFinished(byte[] finalActivity, long timestampNs) {
+        }
+
+        @Override
+        public void onReportFullyDrawn(byte[] finalActivity, long timestampNs) {
         }
     };
 
@@ -8658,7 +8662,7 @@
         lp.format = v.getBackground().getOpacity();
         lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         ((WindowManager)mContext.getSystemService(
                 Context.WINDOW_SERVICE)).addView(v, lp);
     }
diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java
index a80a5b5..852c9b65 100644
--- a/services/core/java/com/android/server/am/AppErrorDialog.java
+++ b/services/core/java/com/android/server/am/AppErrorDialog.java
@@ -92,7 +92,7 @@
         WindowManager.LayoutParams attrs = getWindow().getAttributes();
         attrs.setTitle("Application Error: " + mProc.info.processName);
         attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR
-                | WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+                | WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         getWindow().setAttributes(attrs);
         if (mProc.isPersistent()) {
             getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
index cb76e2f..65d7e01 100644
--- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java
+++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
@@ -94,7 +94,7 @@
         WindowManager.LayoutParams attrs = getWindow().getAttributes();
         attrs.setTitle("Application Not Responding: " + mProc.info.processName);
         attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR |
-                WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+                WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         getWindow().setAttributes(attrs);
     }
 
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 98f5557..bbf5772 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -78,7 +78,7 @@
 
         WindowManager.LayoutParams attrs = getWindow().getAttributes();
         attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR |
-            WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+            WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         getWindow().setAttributes(attrs);
     }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6c4cc2d..2ac6eb0 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -68,6 +68,7 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -1398,6 +1399,12 @@
     }
 
     private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
+        PackageManagerInternal packageManagerInternal = LocalServices.getService(
+                PackageManagerInternal.class);
+        if (packageManagerInternal.getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M) {
+            return;
+        }
+
         PackageManager packageManager = mContext.getPackageManager();
         String[] packageNames = packageManager.getPackagesForUid(uid);
         if (ArrayUtils.isEmpty(packageNames)) {
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/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 027e2fb..0fabd9a 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -25,6 +25,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.server.compat.config.Change;
 import com.android.server.compat.config.XmlParser;
 
@@ -186,6 +187,43 @@
         }
         return overrideExists;
     }
+    /**
+     * Overrides the enabled state for a given change and app. This method is intended to be used
+     * *only* for debugging purposes.
+     *
+     * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
+     *
+     * @param overrides list of overrides to default changes config.
+     * @param packageName app for which the overrides will be applied.
+     */
+    public void addOverrides(
+            CompatibilityChangeConfig overrides, String packageName) {
+        synchronized (mChanges) {
+            for (Long changeId: overrides.enabledChanges()) {
+                addOverride(changeId, packageName, true);
+            }
+            for (Long changeId: overrides.disabledChanges()) {
+                addOverride(changeId, packageName, false);
+            }
+        }
+    }
+
+    /**
+     * Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or
+     * {@link #addAppOverrides(CompatibilityChangeConfig, String)} for a certain package.
+     *
+     * <p>This restores the default behaviour for the given change and app, once any app
+     * processes have been restarted.
+     *
+     * @param packageName The package for which the overrides should be purged.
+     */
+    public void removePackageOverrides(String packageName) {
+        synchronized (mChanges) {
+            for (int i = 0; i < mChanges.size(); ++i) {
+                mChanges.valueAt(i).removePackageOverride(packageName);
+            }
+        }
+    }
 
     /**
     * Dumps the current list of compatibility config information.
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index a737888..8a7dcc1 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -23,6 +23,7 @@
 import android.util.StatsLog;
 
 import com.android.internal.compat.ChangeReporter;
+import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.util.DumpUtils;
 
@@ -100,6 +101,17 @@
     }
 
     @Override
+    public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
+        CompatConfig.get().addOverrides(overrides, packageName);
+    }
+
+    @Override
+    public void clearOverrides(String packageName) {
+        CompatConfig config = CompatConfig.get();
+        config.removePackageOverrides(packageName);
+    }
+
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
         CompatConfig.get().dumpConfig(pw);
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 96ba8ef..648e07a 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -394,14 +394,10 @@
     static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "persist.sys.hdmi.addr.playback";
     static final String PROPERTY_PREFERRED_ADDRESS_TV = "persist.sys.hdmi.addr.tv";
 
-
     // TODO(OEM): Set this to false to keep the playback device in sleep upon hotplug event.
     //            True by default.
     static final String PROPERTY_WAKE_ON_HOTPLUG = "ro.hdmi.wake_on_hotplug";
 
-    // TODO(OEM): Set this to true to enable 'Set Menu Language' feature. False by default.
-    static final String PROPERTY_SET_MENU_LANGUAGE = "ro.hdmi.set_menu_language";
-
     /**
      * Property to save the ARC port id on system audio device.
      * <p>When ARC is initiated, this port will be used to turn on ARC.
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 61d4d4b..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;
         }
@@ -1238,8 +1239,8 @@
         }
         // Wake up if the current device if ready to route.
         mService.wakeUp();
-        if (getLocalActivePort() == portId) {
-            HdmiLogger.debug("Not switching to the same port " + portId);
+        if ((getLocalActivePort() == portId) && (portId != Constants.CEC_SWITCH_ARC)) {
+            HdmiLogger.debug("Not switching to the same port " + portId + " except for arc");
             return;
         }
         // Switch to HOME if the current active port is not HOME yet
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 413e7a0..0944324 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -24,6 +24,7 @@
 import android.os.PowerManager.WakeLock;
 import android.os.SystemProperties;
 import android.provider.Settings.Global;
+import android.sysprop.HdmiProperties;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -47,7 +48,7 @@
             SystemProperties.getBoolean(Constants.PROPERTY_WAKE_ON_HOTPLUG, true);
 
     private static final boolean SET_MENU_LANGUAGE =
-            SystemProperties.getBoolean(Constants.PROPERTY_SET_MENU_LANGUAGE, false);
+            HdmiProperties.set_menu_language().orElse(false);
 
     // Used to keep the device awake while it is the active source. For devices that
     // cannot wake up via CEC commands, this address the inconvenience of having to
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 7e6e668..362955d 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -71,7 +71,6 @@
 import android.view.IInputFilter;
 import android.view.IInputFilterHost;
 import android.view.IInputMonitorHost;
-import android.view.IWindow;
 import android.view.InputApplicationHandle;
 import android.view.InputChannel;
 import android.view.InputDevice;
@@ -186,9 +185,6 @@
     IInputFilter mInputFilter; // guarded by mInputFilterLock
     InputFilterHost mInputFilterHost; // guarded by mInputFilterLock
 
-    private IWindow mFocusedWindow;
-    private boolean mFocusedWindowHasCapture;
-
     private static native long nativeInit(InputManagerService service,
             Context context, MessageQueue messageQueue);
     private static native void nativeStart(long ptr);
@@ -203,8 +199,7 @@
             int deviceId, int sourceMask, int sw);
     private static native boolean nativeHasKeys(long ptr,
             int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
-    private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel,
-            int displayId);
+    private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel);
     private static native void nativeRegisterInputMonitor(long ptr, InputChannel inputChannel,
             int displayId, boolean isGestureMonitor);
     private static native void nativeUnregisterInputChannel(long ptr, InputChannel inputChannel);
@@ -529,7 +524,6 @@
             throw new IllegalArgumentException("displayId must >= 0.");
         }
 
-
         final long ident = Binder.clearCallingIdentity();
         try {
             InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
@@ -537,29 +531,25 @@
             inputChannels[0].setToken(host.asBinder());
             nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId,
                     true /*isGestureMonitor*/);
-            return new InputMonitor(inputChannelName, inputChannels[1], host);
+            return new InputMonitor(inputChannels[1], host);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
     /**
-     * Registers an input channel so that it can be used as an input event target.
+     * Registers an input channel so that it can be used as an input event target. The channel is
+     * registered with a generated token.
+     *
      * @param inputChannel The input channel to register.
-     * @param inputWindowHandle The handle of the input window associated with the
-     * input channel, or null if none.
      */
-    public void registerInputChannel(InputChannel inputChannel, IBinder token) {
+    public void registerInputChannel(InputChannel inputChannel) {
         if (inputChannel == null) {
             throw new IllegalArgumentException("inputChannel must not be null.");
         }
+        inputChannel.setToken(new Binder());
 
-        if (token == null) {
-            token = new Binder();
-        }
-        inputChannel.setToken(token);
-
-        nativeRegisterInputChannel(mPtr, inputChannel, Display.INVALID_DISPLAY);
+        nativeRegisterInputChannel(mPtr, inputChannel);
     }
 
     /**
@@ -1513,26 +1503,9 @@
 
     @Override
     public void requestPointerCapture(IBinder windowToken, boolean enabled) {
-        if (mFocusedWindow == null || mFocusedWindow.asBinder() != windowToken) {
-            Slog.e(TAG, "requestPointerCapture called for a window that has no focus: "
-                    + windowToken);
-            return;
-        }
-        if (mFocusedWindowHasCapture == enabled) {
-            Slog.i(TAG, "requestPointerCapture: already " + (enabled ? "enabled" : "disabled"));
-            return;
-        }
-        setPointerCapture(enabled);
-    }
-
-    private void setPointerCapture(boolean enabled) {
-        if (mFocusedWindowHasCapture != enabled) {
-            mFocusedWindowHasCapture = enabled;
-            try {
-                mFocusedWindow.dispatchPointerCaptureChanged(enabled);
-            } catch (RemoteException ex) {
-                /* ignore */
-            }
+        boolean requestConfigurationRefresh =
+                mWindowManagerCallbacks.requestPointerCapture(windowToken, enabled);
+        if (requestConfigurationRefresh) {
             nativeSetPointerCapture(mPtr, enabled);
         }
     }
@@ -1829,16 +1802,11 @@
 
     // Native callback
     private void notifyFocusChanged(IBinder oldToken, IBinder newToken) {
-        if (mFocusedWindow != null) {
-            if (mFocusedWindow.asBinder() == newToken) {
-                Slog.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow="
-                        + mFocusedWindow);
-                return;
-            }
-            setPointerCapture(false);
+        final boolean requestConfigurationRefresh =
+                mWindowManagerCallbacks.notifyFocusChanged(oldToken, newToken);
+        if (requestConfigurationRefresh) {
+            nativeSetPointerCapture(mPtr, false);
         }
-
-        mFocusedWindow = IWindow.Stub.asInterface(newToken);
     }
 
     // Native callback.
@@ -2116,6 +2084,20 @@
          * @param touchedToken The token for the window that received the input event.
          */
         void onPointerDownOutsideFocus(IBinder touchedToken);
+
+        /**
+         * Called when the focused window has changed.
+         *
+         * @return true if we want to request a configuration refresh.
+         */
+        boolean notifyFocusChanged(IBinder oldToken, IBinder newToken);
+
+        /**
+         * Called by the client to request pointer capture.
+         *
+         * @return true if we want to request a configuration refresh.
+         */
+        boolean requestPointerCapture(IBinder windowToken, boolean enabled);
     }
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b7fcd3f..471fa72 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3570,10 +3570,14 @@
                 return;
             }
             if (!setVisible) {
-                // Client hides the IME directly.
-                if (mCurClient != null && mCurClient.client != null) {
-                    executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
-                            MSG_APPLY_IME_VISIBILITY, setVisible ? 1 : 0, mCurClient));
+                if (mCurClient != null) {
+                    // IMMS only knows of focused window, not the actual IME target.
+                    // e.g. it isn't aware of any window that has both
+                    // NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target.
+                    // Send it to window manager to hide IME from IME target window.
+                    // TODO(b/139861270): send to mCurClient.client once IMMS is aware of
+                    // actual IME target.
+                    mWindowManagerInternal.hideIme(mCurClient.selfReportedDisplayId);
                 }
             } else {
                 // Send to window manager to show IME after IME layout finishes.
@@ -4208,7 +4212,7 @@
             // with other IME windows based on type vs. grouping based on whichever token happens
             // to get selected by the system later on.
             attrs.token = mSwitchingDialogToken;
-            attrs.privateFlags |= LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+            attrs.privateFlags |= LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
             attrs.setTitle("Select input method");
             w.setAttributes(attrs);
             updateSystemUiLocked(mImeWindowVis, mBackDisposition);
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
index 4ed581d..6416505 100644
--- a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
+++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
@@ -37,6 +37,9 @@
     /**
      * Match the list of rules against an app install metadata.
      *
+     * <p>Rules must be in disjunctive normal form (DNF). A rule should contain AND'ed formulas
+     * only. All rules are OR'ed together by default.
+     *
      * @param rules              The list of rules to evaluate.
      * @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules
      *                           against.
@@ -45,7 +48,7 @@
      */
     static Rule evaluateRules(List<Rule> rules, AppInstallMetadata appInstallMetadata) {
         for (Rule rule : rules) {
-            if (isMatch(rule, appInstallMetadata)) {
+            if (isConjunctionOfFormulas(rule.getFormula()) && isMatch(rule, appInstallMetadata)) {
                 return rule;
             }
         }
@@ -88,11 +91,8 @@
                     // NOT connector has only 1 formula attached.
                     return !isMatch(openFormula.getFormulas().get(0), appInstallMetadata);
                 case AND:
-                    boolean result = true;
-                    for (Formula subFormula : openFormula.getFormulas()) {
-                        result &= isMatch(subFormula, appInstallMetadata);
-                    }
-                    return result;
+                    return openFormula.getFormulas().stream().allMatch(
+                            subFormula -> isMatch(subFormula, appInstallMetadata));
                 default:
                     Slog.i(TAG, String.format("Returned no match for unknown connector %s",
                             openFormula.getConnector()));
@@ -102,4 +102,25 @@
 
         return false;
     }
+
+    private static boolean isConjunctionOfFormulas(Formula formula) {
+        if (formula == null) {
+            return false;
+        }
+        if (isAtomicFormula(formula)) {
+            return true;
+        }
+        OpenFormula openFormula = (OpenFormula) formula;
+        return openFormula.getConnector() == OpenFormula.Connector.AND
+                && openFormula.getFormulas().stream().allMatch(RuleEvaluator::isAtomicFormula);
+    }
+
+    private static boolean isAtomicFormula(Formula formula) {
+        if (formula instanceof AtomicFormula) {
+            return true;
+        }
+        OpenFormula openFormula = (OpenFormula) formula;
+        return openFormula.getConnector() == OpenFormula.Connector.NOT
+                && openFormula.getFormulas().get(0) instanceof AtomicFormula;
+    }
 }
diff --git a/services/core/java/com/android/server/integrity/model/AtomicFormula.java b/services/core/java/com/android/server/integrity/model/AtomicFormula.java
index dde8c2a..b9b46e3 100644
--- a/services/core/java/com/android/server/integrity/model/AtomicFormula.java
+++ b/services/core/java/com/android/server/integrity/model/AtomicFormula.java
@@ -22,6 +22,8 @@
 import android.annotation.Nullable;
 import android.util.Slog;
 
+import java.util.Objects;
+
 /**
  * Represents a simple formula consisting of an app install metadata field and a value.
  *
@@ -130,11 +132,6 @@
         return mBoolValue.toString();
     }
 
-    @Override
-    public String toString() {
-        return String.format("%s %s %s", mKey, mOperator, getValue());
-    }
-
     /**
      * Check if the formula is true when substituting its {@link Key} with the string value.
      *
@@ -188,6 +185,32 @@
         return false;
     }
 
+    @Override
+    public String toString() {
+        return String.format("%s %s %s", mKey, mOperator, getValue());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        AtomicFormula that = (AtomicFormula) o;
+        return mKey == that.mKey
+                && mOperator == that.mOperator
+                && Objects.equals(mStringValue, that.mStringValue)
+                && Objects.equals(mIntValue, that.mIntValue)
+                && Objects.equals(mBoolValue, that.mBoolValue);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mKey, mOperator, mStringValue, mIntValue, mBoolValue);
+    }
+
     private void validateOperator(Key key, Operator operator) {
         boolean validOperator;
         switch (key) {
diff --git a/services/core/java/com/android/server/integrity/model/OpenFormula.java b/services/core/java/com/android/server/integrity/model/OpenFormula.java
index 5ba9f46..21da629 100644
--- a/services/core/java/com/android/server/integrity/model/OpenFormula.java
+++ b/services/core/java/com/android/server/integrity/model/OpenFormula.java
@@ -21,6 +21,7 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Represents a complex formula consisting of other simple and complex formulas.
@@ -64,6 +65,24 @@
         return sb.toString();
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        OpenFormula that = (OpenFormula) o;
+        return mConnector == that.mConnector
+                && mFormulas.equals(that.mFormulas);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mConnector, mFormulas);
+    }
+
     private void validateFormulas(Connector connector, List<Formula> formulas) {
         switch (connector) {
             case AND:
diff --git a/services/core/java/com/android/server/integrity/model/Rule.java b/services/core/java/com/android/server/integrity/model/Rule.java
index 41611d0..63b9b91 100644
--- a/services/core/java/com/android/server/integrity/model/Rule.java
+++ b/services/core/java/com/android/server/integrity/model/Rule.java
@@ -18,6 +18,8 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
+import java.util.Objects;
+
 /**
  * Represent rules to be used in the rule evaluation engine to match against app installs.
  *
@@ -66,4 +68,22 @@
     public String toString() {
         return String.format("Rule: %s, %s", mFormula, mEffect);
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        Rule that = (Rule) o;
+        return Objects.equals(mFormula, that.mFormula)
+                && Objects.equals(mEffect, that.mEffect);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mFormula, mEffect);
+    }
 }
diff --git a/services/core/java/com/android/server/location/CallerIdentity.java b/services/core/java/com/android/server/location/CallerIdentity.java
index da31d0b..61e5d1f 100644
--- a/services/core/java/com/android/server/location/CallerIdentity.java
+++ b/services/core/java/com/android/server/location/CallerIdentity.java
@@ -16,6 +16,8 @@
 
 package com.android.server.location;
 
+import android.annotation.NonNull;
+
 /**
  * Represents the calling process's uid, pid, and package name.
  */
@@ -23,10 +25,13 @@
     public final int mUid;
     public final int mPid;
     public final String mPackageName;
+    public final @NonNull String mListenerIdentifier;
 
-    public CallerIdentity(int uid, int pid, String packageName) {
+    public CallerIdentity(int uid, int pid, String packageName,
+            @NonNull String listenerIdentifier) {
         mUid = uid;
         mPid = pid;
         mPackageName = packageName;
+        mListenerIdentifier = listenerIdentifier;
     }
 }
diff --git a/services/core/java/com/android/server/location/GeofenceManager.java b/services/core/java/com/android/server/location/GeofenceManager.java
index a192206..895c4b3 100644
--- a/services/core/java/com/android/server/location/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/GeofenceManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.location;
 
+import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.ContentResolver;
@@ -151,14 +152,16 @@
     }
 
     public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
-            int allowedResolutionLevel, int uid, String packageName) {
+            int allowedResolutionLevel, int uid, String packageName,
+            @NonNull String listenerIdentifier) {
         if (D) {
             Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence
                     + ", intent=" + intent + ", uid=" + uid + ", packageName=" + packageName);
         }
 
         GeofenceState state = new GeofenceState(geofence,
-                request.getExpireAt(), allowedResolutionLevel, uid, packageName, intent);
+                request.getExpireAt(), allowedResolutionLevel, uid, packageName, listenerIdentifier,
+                intent);
         synchronized (mLock) {
             // first make sure it doesn't already exist
             for (int i = mFences.size() - 1; i >= 0; i--) {
@@ -301,7 +304,8 @@
                 int op = LocationManagerService.resolutionLevelToOp(state.mAllowedResolutionLevel);
                 if (op >= 0) {
                     if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, state.mUid,
-                            state.mPackageName) != AppOpsManager.MODE_ALLOWED) {
+                            state.mPackageName, state.mListenerIdentifier)
+                            != AppOpsManager.MODE_ALLOWED) {
                         if (D) {
                             Slog.d(TAG, "skipping geofence processing for no op app: "
                                     + state.mPackageName);
diff --git a/services/core/java/com/android/server/location/GeofenceState.java b/services/core/java/com/android/server/location/GeofenceState.java
index 3ebe20a..fe0719d 100644
--- a/services/core/java/com/android/server/location/GeofenceState.java
+++ b/services/core/java/com/android/server/location/GeofenceState.java
@@ -17,6 +17,7 @@
 
 package com.android.server.location;
 
+import android.annotation.NonNull;
 import android.app.PendingIntent;
 import android.location.Geofence;
 import android.location.Location;
@@ -38,13 +39,14 @@
     public final int mAllowedResolutionLevel;
     public final int mUid;
     public final String mPackageName;
+    public final @NonNull String mListenerIdentifier;
     public final PendingIntent mIntent;
 
     int mState;  // current state
     double mDistanceToCenter;  // current distance to center of fence
 
-    public GeofenceState(Geofence fence, long expireAt,
-            int allowedResolutionLevel, int uid, String packageName, PendingIntent intent) {
+    public GeofenceState(Geofence fence, long expireAt, int allowedResolutionLevel, int uid,
+            String packageName, @NonNull String listenerIdentifier, PendingIntent intent) {
         mState = STATE_UNKNOWN;
         mDistanceToCenter = Double.MAX_VALUE;
 
@@ -53,6 +55,7 @@
         mAllowedResolutionLevel = allowedResolutionLevel;
         mUid = uid;
         mPackageName = packageName;
+        mListenerIdentifier = listenerIdentifier;
         mIntent = intent;
 
         mLocation = new Location("");
diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java
index aa8a25a..0929d93 100644
--- a/services/core/java/com/android/server/location/RemoteListenerHelper.java
+++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java
@@ -182,7 +182,9 @@
         }
 
         return mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, callerIdentity.mUid,
-                callerIdentity.mPackageName) == AppOpsManager.MODE_ALLOWED;
+                callerIdentity.mPackageName,
+                "Location sent to " + callerIdentity.mListenerIdentifier)
+                == AppOpsManager.MODE_ALLOWED;
     }
 
     protected void logPermissionDisabledEventNotReported(String tag, String packageName,
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index bad484f..34fb641 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -118,6 +118,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternUtils.CredentialType;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
@@ -2058,7 +2059,8 @@
             @UserIdInt int userHandle) {
         synchronized (this) {
             mUserPasswordMetrics.put(userHandle,
-                    PasswordMetrics.computeForCredential(credentialType, password));
+                    PasswordMetrics.computeForCredential(
+                            LockscreenCredential.createRaw(credentialType, password)));
         }
     }
 
@@ -2069,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/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index a5d59e3..0a8e5bd 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -16,16 +16,12 @@
 
 package com.android.server.locksettings;
 
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-
-import static com.android.internal.widget.LockPatternUtils.stringToPattern;
-
 import android.app.ActivityManager;
 import android.os.ShellCommand;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
+import com.android.internal.widget.LockscreenCredential;
 
 import java.io.PrintWriter;
 
@@ -189,31 +185,49 @@
                 mLockPatternUtils.isSyntheticPasswordEnabled()));
     }
 
+    private LockscreenCredential getOldCredential() {
+        if (mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId)) {
+            final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(mCurrentUserId);
+            if (LockPatternUtils.isQualityAlphabeticPassword(quality)) {
+                return LockscreenCredential.createPassword(mOld);
+            } else {
+                return LockscreenCredential.createPin(mOld);
+            }
+        } else if (mLockPatternUtils.isLockPatternEnabled(mCurrentUserId)) {
+            return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+                    mOld.getBytes()));
+        } else {
+            return LockscreenCredential.createNone();
+        }
+    }
+
     private void runSetPattern() {
-        byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
-        mLockPatternUtils.saveLockPattern(stringToPattern(mNew), oldBytes, mCurrentUserId);
+        mLockPatternUtils.setLockCredential(
+                LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+                        mNew.getBytes())),
+                getOldCredential(),
+                mCurrentUserId);
         getOutPrintWriter().println("Pattern set to '" + mNew + "'");
     }
 
     private void runSetPassword() {
-        byte[] newBytes = mNew != null ? mNew.getBytes() : null;
-        byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
-        mLockPatternUtils.saveLockPassword(newBytes, oldBytes, PASSWORD_QUALITY_ALPHABETIC,
+        mLockPatternUtils.setLockCredential(LockscreenCredential.createPassword(mNew),
+                getOldCredential(),
                 mCurrentUserId);
         getOutPrintWriter().println("Password set to '" + mNew + "'");
     }
 
     private void runSetPin() {
-        byte[] newBytes = mNew != null ? mNew.getBytes() : null;
-        byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
-        mLockPatternUtils.saveLockPassword(newBytes, oldBytes, PASSWORD_QUALITY_NUMERIC,
+        mLockPatternUtils.setLockCredential(LockscreenCredential.createPin(mNew),
+                getOldCredential(),
                 mCurrentUserId);
         getOutPrintWriter().println("Pin set to '" + mNew + "'");
     }
 
     private void runClear() {
-        byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
-        mLockPatternUtils.clearLock(oldBytes, mCurrentUserId);
+        mLockPatternUtils.setLockCredential(LockscreenCredential.createNone(),
+                getOldCredential(),
+                mCurrentUserId);
         getOutPrintWriter().println("Lock credential cleared");
     }
 
@@ -238,13 +252,8 @@
             }
 
             try {
-                final boolean result;
-                if (havePassword) {
-                    byte[] passwordBytes = mOld != null ? mOld.getBytes() : null;
-                    result = mLockPatternUtils.checkPassword(passwordBytes, mCurrentUserId);
-                } else {
-                    result = mLockPatternUtils.checkPattern(stringToPattern(mOld), mCurrentUserId);
-                }
+                final boolean result = mLockPatternUtils.checkCredential(getOldCredential(),
+                        mCurrentUserId, null);
                 if (!result) {
                     if (!mLockPatternUtils.isManagedProfileWithUnifiedChallenge(mCurrentUserId)) {
                         mLockPatternUtils.reportFailedPasswordAttempt(mCurrentUserId);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 5594614..f4cad63 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -95,8 +95,6 @@
 
     @VisibleForTesting
     public static class CredentialHash {
-        /** Deprecated private static final int VERSION_LEGACY = 0; */
-        private static final int VERSION_GATEKEEPER = 1;
 
         private CredentialHash(byte[] hash, @CredentialType int type) {
             if (type != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
@@ -126,42 +124,6 @@
 
         byte[] hash;
         @CredentialType int type;
-
-        public byte[] toBytes() {
-            try {
-                ByteArrayOutputStream os = new ByteArrayOutputStream();
-                DataOutputStream dos = new DataOutputStream(os);
-                dos.write(VERSION_GATEKEEPER);
-                dos.write(type);
-                if (hash != null && hash.length > 0) {
-                    dos.writeInt(hash.length);
-                    dos.write(hash);
-                } else {
-                    dos.writeInt(0);
-                }
-                dos.close();
-                return os.toByteArray();
-            } catch (IOException e) {
-                throw new IllegalStateException("Fail to serialze credential hash", e);
-            }
-        }
-
-        public static CredentialHash fromBytes(byte[] bytes) {
-            try {
-                DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
-                /* int version = */ is.read();
-                int type = is.read();
-                int hashSize = is.readInt();
-                byte[] hash = null;
-                if (hashSize > 0) {
-                    hash = new byte[hashSize];
-                    is.readFully(hash);
-                }
-                return new CredentialHash(hash, type);
-            } catch (IOException e) {
-                throw new IllegalStateException("Fail to deserialze credential hash", e);
-            }
-        }
     }
 
     public LockSettingsStorage(Context context) {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index e753a7b..9eac252 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -93,7 +93,7 @@
 
     public void unselectRoute(String packageName, String routeId) {
         if (mConnectionReady) {
-            mActiveConnection.unselectRotue(packageName, routeId);
+            mActiveConnection.unselectRoute(packageName, routeId);
             updateBinding();
         }
     }
@@ -105,6 +105,20 @@
         }
     }
 
+    public void requestSetVolume(MediaRoute2Info route, int volume) {
+        if (mConnectionReady) {
+            mActiveConnection.requestSetVolume(route.getId(), volume);
+            updateBinding();
+        }
+    }
+
+    public void requestUpdateVolume(MediaRoute2Info route, int delta) {
+        if (mConnectionReady) {
+            mActiveConnection.requestUpdateVolume(route.getId(), delta);
+            updateBinding();
+        }
+    }
+
     @Nullable
     public MediaRoute2ProviderInfo getProviderInfo() {
         return mProviderInfo;
@@ -324,7 +338,7 @@
             }
         }
 
-        public void unselectRotue(String packageName, String routeId) {
+        public void unselectRoute(String packageName, String routeId) {
             try {
                 mProvider.unselectRoute(packageName, routeId);
             } catch (RemoteException ex) {
@@ -340,6 +354,22 @@
             }
         }
 
+        public void requestSetVolume(String routeId, int volume) {
+            try {
+                mProvider.requestSetVolume(routeId, volume);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to deliver request to request set volume.", ex);
+            }
+        }
+
+        public void requestUpdateVolume(String routeId, int delta) {
+            try {
+                mProvider.requestUpdateVolume(routeId, delta);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to deliver request to request update volume.", ex);
+            }
+        }
+
         @Override
         public void binderDied() {
             mHandler.post(() -> onConnectionDied(Connection.this));
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 668f2be..74d59ac 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -198,6 +198,34 @@
         }
     }
 
+    public void requestSetVolume2(IMediaRouter2Client client, MediaRoute2Info route, int volume) {
+        Objects.requireNonNull(client, "client must not be null");
+        Objects.requireNonNull(route, "route must not be null");
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                requestSetVolumeLocked(client, route, volume);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) {
+        Objects.requireNonNull(client, "client must not be null");
+        Objects.requireNonNull(route, "route must not be null");
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                requestUpdateVolumeLocked(client, route, delta);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     public void selectClientRoute2(@NonNull IMediaRouter2Manager manager,
             String packageName, @Nullable MediaRoute2Info route) {
         final long token = Binder.clearCallingIdentity();
@@ -210,6 +238,37 @@
         }
     }
 
+    public void requestSetVolume2Manager(IMediaRouter2Manager manager,
+            MediaRoute2Info route, int volume) {
+        Objects.requireNonNull(manager, "manager must not be null");
+        Objects.requireNonNull(route, "route must not be null");
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                requestSetVolumeLocked(manager, route, volume);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
+            MediaRoute2Info route, int delta) {
+        Objects.requireNonNull(manager, "manager must not be null");
+        Objects.requireNonNull(route, "route must not be null");
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                requestUpdateVolumeLocked(manager, route, delta);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+
     public void registerClient(@NonNull IMediaRouterClient client, @NonNull String packageName) {
         Objects.requireNonNull(client, "client must not be null");
 
@@ -362,6 +421,30 @@
         }
     }
 
+    private void requestSetVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route,
+            int volume) {
+        final IBinder binder = client.asBinder();
+        ClientRecord clientRecord = mAllClientRecords.get(binder);
+
+        if (clientRecord != null) {
+            clientRecord.mUserRecord.mHandler.sendMessage(
+                    obtainMessage(UserHandler::requestSetVolume,
+                            clientRecord.mUserRecord.mHandler, route, volume));
+        }
+    }
+
+    private void requestUpdateVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route,
+            int delta) {
+        final IBinder binder = client.asBinder();
+        ClientRecord clientRecord = mAllClientRecords.get(binder);
+
+        if (clientRecord != null) {
+            clientRecord.mUserRecord.mHandler.sendMessage(
+                    obtainMessage(UserHandler::requestUpdateVolume,
+                            clientRecord.mUserRecord.mHandler, route, delta));
+        }
+    }
+
     private void registerManagerLocked(IMediaRouter2Manager manager,
             int uid, int pid, String packageName, int userId, boolean trusted) {
         final IBinder binder = manager.asBinder();
@@ -424,6 +507,31 @@
         }
     }
 
+    private void requestSetVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route,
+            int volume) {
+        final IBinder binder = manager.asBinder();
+        ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+        if (managerRecord != null) {
+            managerRecord.mUserRecord.mHandler.sendMessage(
+                    obtainMessage(UserHandler::requestSetVolume,
+                            managerRecord.mUserRecord.mHandler, route, volume));
+        }
+    }
+
+    private void requestUpdateVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route,
+            int delta) {
+        final IBinder binder = manager.asBinder();
+        ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+        if (managerRecord != null) {
+            managerRecord.mUserRecord.mHandler.sendMessage(
+                    obtainMessage(UserHandler::requestUpdateVolume,
+                            managerRecord.mUserRecord.mHandler, route, delta));
+        }
+    }
+
+
     private void initializeUserLocked(UserRecord userRecord) {
         if (DEBUG) {
             Slog.d(TAG, userRecord + ": Initialized");
@@ -679,6 +787,20 @@
             }
         }
 
+        private void requestSetVolume(MediaRoute2Info route, int volume) {
+            final MediaRoute2ProviderProxy provider = findProvider(route.getProviderId());
+            if (provider != null) {
+                provider.requestSetVolume(route, volume);
+            }
+        }
+
+        private void requestUpdateVolume(MediaRoute2Info route, int delta) {
+            final MediaRoute2ProviderProxy provider = findProvider(route.getProviderId());
+            if (provider != null) {
+                provider.requestUpdateVolume(route, delta);
+            }
+        }
+
         private void scheduleUpdateProviderInfos() {
             if (!mProviderInfosUpdateScheduled) {
                 mProviderInfosUpdateScheduled = true;
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 796a25d..afd92f6 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -499,6 +499,32 @@
         mService2.setControlCategories2(client, categories);
     }
 
+    // Binder call
+    @Override
+    public void requestSetVolume2(IMediaRouter2Client client, MediaRoute2Info route, int volume) {
+        mService2.requestSetVolume2(client, route, volume);
+    }
+
+    // Binder call
+    @Override
+    public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) {
+        mService2.requestUpdateVolume2(client, route, delta);
+    }
+
+    // Binder call
+    @Override
+    public void requestSetVolume2Manager(IMediaRouter2Manager manager,
+            MediaRoute2Info route, int volume) {
+        mService2.requestSetVolume2Manager(manager, route, volume);
+    }
+
+    // Binder call
+    @Override
+    public void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
+            MediaRoute2Info route, int delta) {
+        mService2.requestUpdateVolume2Manager(manager, route, delta);
+    }
+
     void restoreBluetoothA2dp() {
         try {
             boolean a2dpOn;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 976a0c6..812ce32 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -217,7 +217,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.os.SomeArgs;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
@@ -385,6 +384,7 @@
     private static final int MSG_SUBSCRIPTION_OVERRIDE = 16;
     private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17;
     private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18;
+    private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19;
 
     private static final int UID_MSG_STATE_CHANGED = 100;
     private static final int UID_MSG_GONE = 101;
@@ -1509,6 +1509,11 @@
         latch.await(5, TimeUnit.SECONDS);
     }
 
+    @VisibleForTesting
+    Handler getHandlerForTesting() {
+        return mHandler;
+    }
+
     /**
      * Update mobile policies with data cycle information from {@link CarrierConfigManager}
      * if necessary.
@@ -3064,6 +3069,34 @@
         mContext.enforceCallingOrSelfPermission(MANAGE_SUBSCRIPTION_PLANS, TAG);
     }
 
+    private void enforceSubscriptionPlanValidity(SubscriptionPlan[] plans) {
+        // nothing to check if no plans
+        if (plans.length == 0) {
+            return;
+        }
+
+        long applicableNetworkTypes = 0;
+        boolean allNetworks = false;
+        for (SubscriptionPlan plan : plans) {
+            if (plan.getNetworkTypes() == null) {
+                allNetworks = true;
+            } else {
+                if ((applicableNetworkTypes & plan.getNetworkTypesBitMask()) != 0) {
+                    throw new IllegalArgumentException(
+                            "Multiple subscription plans defined for a single network type.");
+                } else {
+                    applicableNetworkTypes |= plan.getNetworkTypesBitMask();
+                }
+            }
+        }
+
+        // ensure at least one plan applies for every network type
+        if (!allNetworks) {
+            throw new IllegalArgumentException(
+                    "No generic subscription plan that applies to all network types.");
+        }
+    }
+
     @Override
     public SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage) {
         enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
@@ -3228,6 +3261,7 @@
     @Override
     public void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage) {
         enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
+        enforceSubscriptionPlanValidity(plans);
 
         for (SubscriptionPlan plan : plans) {
             Preconditions.checkNotNull(plan);
@@ -3256,6 +3290,8 @@
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
             mContext.sendBroadcast(intent, android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS);
+            mHandler.sendMessage(
+                    mHandler.obtainMessage(MSG_SUBSCRIPTION_PLANS_CHANGED, subId, 0, plans));
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -3282,7 +3318,7 @@
 
     @Override
     public void setSubscriptionOverride(int subId, int overrideMask, int overrideValue,
-            long networkTypeMask, long timeoutMillis, String callingPackage) {
+            long timeoutMillis, String callingPackage) {
         enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
 
         // We can only override when carrier told us about plans
@@ -3300,16 +3336,11 @@
         final boolean overrideEnabled = Settings.Global.getInt(mContext.getContentResolver(),
                 NETPOLICY_OVERRIDE_ENABLED, 1) != 0;
         if (overrideEnabled || overrideValue == 0) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = subId;
-            args.arg2 = overrideMask;
-            args.arg3 = overrideValue;
-            args.arg4 = networkTypeMask;
-            mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args));
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
+                    overrideMask, overrideValue, subId));
             if (timeoutMillis > 0) {
-                args.arg3 = 0;
-                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args),
-                        timeoutMillis);
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
+                        overrideMask, 0, subId), timeoutMillis);
             }
         }
     }
@@ -4445,11 +4476,20 @@
     }
 
     private void dispatchSubscriptionOverride(INetworkPolicyListener listener, int subId,
-            int overrideMask, int overrideValue, long networkTypeMask) {
+            int overrideMask, int overrideValue) {
         if (listener != null) {
             try {
-                listener.onSubscriptionOverride(subId, overrideMask, overrideValue,
-                        networkTypeMask);
+                listener.onSubscriptionOverride(subId, overrideMask, overrideValue);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    private void dispatchSubscriptionPlansChanged(INetworkPolicyListener listener, int subId,
+            SubscriptionPlan[] plans) {
+        if (listener != null) {
+            try {
+                listener.onSubscriptionPlansChanged(subId, plans);
             } catch (RemoteException ignored) {
             }
         }
@@ -4550,16 +4590,13 @@
                     return true;
                 }
                 case MSG_SUBSCRIPTION_OVERRIDE: {
-                    final SomeArgs args = (SomeArgs) msg.obj;
-                    final int subId = (int) args.arg1;
-                    final int overrideMask = (int) args.arg2;
-                    final int overrideValue = (int) args.arg3;
-                    final long networkTypeMask = (long) args.arg4;
+                    final int overrideMask = msg.arg1;
+                    final int overrideValue = msg.arg2;
+                    final int subId = (int) msg.obj;
                     final int length = mListeners.beginBroadcast();
                     for (int i = 0; i < length; i++) {
                         final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
-                        dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue,
-                                networkTypeMask);
+                        dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue);
                     }
                     mListeners.finishBroadcast();
                     return true;
@@ -4576,6 +4613,17 @@
                     setNetworkTemplateEnabledInner(template, enabled);
                     return true;
                 }
+                case MSG_SUBSCRIPTION_PLANS_CHANGED: {
+                    final SubscriptionPlan[] plans = (SubscriptionPlan[]) msg.obj;
+                    final int subId = msg.arg1;
+                    final int length = mListeners.beginBroadcast();
+                    for (int i = 0; i < length; i++) {
+                        final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+                        dispatchSubscriptionPlansChanged(listener, subId, plans);
+                    }
+                    mListeners.finishBroadcast();
+                    return true;
+                }
                 default: {
                     return false;
                 }
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 61be1f5..6f0ad33 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -17,6 +17,7 @@
 package com.android.server.notification;
 
 import android.app.Notification;
+import android.net.Uri;
 import android.service.notification.NotificationStats;
 
 import com.android.internal.statusbar.NotificationVisibility;
@@ -49,6 +50,12 @@
     void onNotificationBubbleChanged(String key, boolean isBubble);
 
     /**
+     * Grant permission to read the specified URI to the package associated with the
+     * NotificationRecord associated with the given key.
+     */
+    void grantInlineReplyUriPermission(String key, Uri uri, int callingUid);
+
+    /**
      * Notifies that smart replies and actions have been added to the UI.
      */
     void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, int smartActionCount,
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4457e9c..cd3343b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -21,6 +21,7 @@
 import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.Notification.FLAG_INSISTENT;
 import static android.app.Notification.FLAG_NO_CLEAR;
 import static android.app.Notification.FLAG_ONGOING_EVENT;
 import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
@@ -55,6 +56,7 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
 import static android.os.UserHandle.USER_NULL;
@@ -1154,6 +1156,56 @@
                 }
             }
         }
+
+        @Override
+        /**
+         * Grant permission to read the specified URI to the package specified in the
+         * NotificationRecord associated with the given key. The callingUid represents the UID of
+         * SystemUI from which this method is being called.
+         *
+         * For this to work, SystemUI must have permission to read the URI when running under the
+         * user associated with the NotificationRecord, and this grant will fail when trying
+         * to grant URI permissions across users.
+         */
+        public void grantInlineReplyUriPermission(String key, Uri uri, int callingUid) {
+            synchronized (mNotificationLock) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                if (r != null) {
+                    IBinder owner = r.permissionOwner;
+                    if (owner == null) {
+                        r.permissionOwner = mUgmInternal.newUriPermissionOwner("NOTIF:" + key);
+                        owner = r.permissionOwner;
+                    }
+                    int uid = callingUid;
+                    int userId = r.sbn.getUserId();
+                    if (userId == UserHandle.USER_ALL) {
+                        userId = USER_SYSTEM;
+                    }
+                    if (UserHandle.getUserId(uid) != userId) {
+                        try {
+                            final String[] pkgs = mPackageManager.getPackagesForUid(callingUid);
+                            if (pkgs == null) {
+                                Log.e(TAG, "Cannot grant uri permission to unknown UID: "
+                                        + callingUid);
+                            }
+                            final String pkg = pkgs[0]; // Get the SystemUI package
+                            // Find the UID for SystemUI for the correct user
+                            uid =  mPackageManager.getPackageUid(pkg, 0, userId);
+                        } catch (RemoteException re) {
+                            Log.e(TAG, "Cannot talk to package manager", re);
+                        }
+                    }
+                    grantUriPermission(owner, uri, uid, r.sbn.getPackageName(), userId);
+                } else {
+                    Log.w(TAG, "No record found for notification key:" + key);
+
+                    // TODO: figure out cancel story. I think it's: sysui needs to tell us
+                    // whenever noitifications held by a lifetimextender go away
+                    // IBinder owner = mUgmInternal.newUriPermissionOwner("InlineReply:" + key);
+                    // pass in userId and package as well as key (key for logging purposes)
+                }
+            }
+        }
     };
 
     @VisibleForTesting
@@ -1183,7 +1235,7 @@
     }
 
     @GuardedBy("mNotificationLock")
-    private void clearSoundLocked() {
+    void clearSoundLocked() {
         mSoundNotificationKey = null;
         long identity = Binder.clearCallingIdentity();
         try {
@@ -1198,7 +1250,7 @@
     }
 
     @GuardedBy("mNotificationLock")
-    private void clearVibrateLocked() {
+    void clearVibrateLocked() {
         mVibrateNotificationKey = null;
         long identity = Binder.clearCallingIdentity();
         try {
@@ -4495,13 +4547,13 @@
         if (record != null && record.getAudioAttributes() != null) {
             if ((mListenerHints & HINT_HOST_DISABLE_NOTIFICATION_EFFECTS) != 0) {
                 if (record.getAudioAttributes().getUsage()
-                        != AudioAttributes.USAGE_VOICE_COMMUNICATION) {
+                        != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
                     return "listenerNoti";
                 }
             }
             if ((mListenerHints & HINT_HOST_DISABLE_CALL_EFFECTS) != 0) {
                 if (record.getAudioAttributes().getUsage()
-                        == AudioAttributes.USAGE_VOICE_COMMUNICATION) {
+                        == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
                     return "listenerCall";
                 }
             }
@@ -5060,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;
@@ -6060,7 +6112,6 @@
                 mIsAutomotive
                         ? record.getImportance() > NotificationManager.IMPORTANCE_DEFAULT
                         : record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT;
-
         // Remember if this notification already owns the notification channels.
         boolean wasBeep = key != null && key.equals(mSoundNotificationKey);
         boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);
@@ -6076,7 +6127,6 @@
         }
 
         if (aboveThreshold && isNotificationForCurrentUser(record)) {
-
             if (mSystemReady && mAudioManager != null) {
                 Uri soundUri = record.getSound();
                 hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
@@ -6091,7 +6141,6 @@
                     vibration = mFallbackVibrationPattern;
                 }
                 hasValidVibrate = vibration != null;
-
                 boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
                 if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
                     if (!sentAccessibilityEvent) {
@@ -6248,11 +6297,29 @@
             return true;
         }
 
+        // A looping ringtone, such as an incoming call is playing
+        if (isLoopingRingtoneNotification(mNotificationsByKey.get(mSoundNotificationKey))
+                || isLoopingRingtoneNotification(
+                        mNotificationsByKey.get(mVibrateNotificationKey))) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @GuardedBy("mNotificationLock")
+    private boolean isLoopingRingtoneNotification(final NotificationRecord playingRecord) {
+        if (playingRecord != null) {
+            if (playingRecord.getAudioAttributes().getUsage() == USAGE_NOTIFICATION_RINGTONE
+                    && (playingRecord.getNotification().flags & FLAG_INSISTENT) != 0) {
+                return true;
+            }
+        }
         return false;
     }
 
     private boolean playSound(final NotificationRecord record, Uri soundUri) {
-        boolean looping = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
+        boolean looping = (record.getNotification().flags & FLAG_INSISTENT) != 0;
         // play notifications if there is no user of exclusive audio focus
         // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or
         //   VIBRATE ringer mode)
@@ -6304,7 +6371,6 @@
                     try {
                         Thread.sleep(waitMs);
                     } catch (InterruptedException e) { }
-
                     // Notifications might be canceled before it actually vibrates due to waitMs,
                     // so need to check the notification still valide for vibrate.
                     synchronized (mNotificationLock) {
@@ -7012,7 +7078,6 @@
     private void grantUriPermission(IBinder owner, Uri uri, int sourceUid, String targetPkg,
             int targetUserId) {
         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
-
         final long ident = Binder.clearCallingIdentity();
         try {
             mUgm.grantUriPermissionFromOwner(owner, sourceUid, targetPkg,
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 9e7b4648..5f3e503 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -17,11 +17,13 @@
 package com.android.server.om;
 
 import static android.app.AppGlobals.getPackageManager;
+import static android.content.Intent.ACTION_OVERLAY_CHANGED;
 import static android.content.Intent.ACTION_PACKAGE_ADDED;
 import static android.content.Intent.ACTION_PACKAGE_CHANGED;
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 import static android.content.Intent.ACTION_USER_ADDED;
 import static android.content.Intent.ACTION_USER_REMOVED;
+import static android.content.Intent.EXTRA_REASON;
 import static android.content.pm.PackageManager.SIGNATURE_MATCH;
 import static android.os.Trace.TRACE_TAG_RRO;
 import static android.os.Trace.traceBegin;
@@ -356,7 +358,11 @@
                     }
                     break;
                 case ACTION_PACKAGE_CHANGED:
-                    onPackageChanged(packageName, userIds);
+                    // ignore the intent if it was sent by the package manager as a result of the
+                    // overlay manager having sent ACTION_OVERLAY_CHANGED
+                    if (!ACTION_OVERLAY_CHANGED.equals(intent.getStringExtra(EXTRA_REASON))) {
+                        onPackageChanged(packageName, userIds);
+                    }
                     break;
                 case ACTION_PACKAGE_REMOVED:
                     if (replacing) {
@@ -885,7 +891,7 @@
             FgThread.getHandler().post(() -> {
                 updateAssets(userId, targetPackageName);
 
-                final Intent intent = new Intent(Intent.ACTION_OVERLAY_CHANGED,
+                final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
                         Uri.fromParts("package", targetPackageName, null));
                 intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
 
diff --git a/services/core/java/com/android/server/os/TEST_MAPPING b/services/core/java/com/android/server/os/TEST_MAPPING
new file mode 100644
index 0000000..502f1e8
--- /dev/null
+++ b/services/core/java/com/android/server/os/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsUsbTests"
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 05b6168..c8179a7 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -131,7 +131,7 @@
 
     private static class FeatureConfigImpl implements FeatureConfig {
         private static final String FILTERING_ENABLED_NAME = "package_query_filtering_enabled";
-        private volatile boolean mFeatureEnabled = true;
+        private volatile boolean mFeatureEnabled = false;
         private CompatConfig mCompatibility;
 
         private FeatureConfigImpl(PackageManagerService.Injector injector) {
@@ -141,12 +141,12 @@
         @Override
         public void onSystemReady() {
             mFeatureEnabled = DeviceConfig.getBoolean(
-                    NAMESPACE_PACKAGE_MANAGER_SERVICE, FILTERING_ENABLED_NAME, true);
+                    NAMESPACE_PACKAGE_MANAGER_SERVICE, FILTERING_ENABLED_NAME, false);
             DeviceConfig.addOnPropertiesChangedListener(
                     NAMESPACE_PACKAGE_MANAGER_SERVICE, FgThread.getExecutor(),
                     properties -> {
                         synchronized (FeatureConfigImpl.this) {
-                            mFeatureEnabled = properties.getBoolean(FILTERING_ENABLED_NAME, true);
+                            mFeatureEnabled = properties.getBoolean(FILTERING_ENABLED_NAME, false);
                         }
                     });
         }
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index 976cdfb..b1eb7e7 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -50,6 +50,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
 import com.android.server.IntentResolver;
 
 import java.io.PrintWriter;
@@ -386,8 +387,11 @@
             addProvidersLocked(pkg, chatty);
             addServicesLocked(pkg, chatty);
         }
-        final String setupWizardPackage = sPackageManagerInternal.getKnownPackageName(
-                PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM);
+        // expect single setupwizard package
+        final String setupWizardPackage = ArrayUtils.firstOrNull(
+                sPackageManagerInternal.getKnownPackageNames(
+                        PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM));
+
         for (int i = newIntents.size() - 1; i >= 0; --i) {
             final PackageParser.ActivityIntentInfo intentInfo = newIntents.get(i);
             final PackageParser.Package disabledPkg = sPackageManagerInternal
@@ -421,8 +425,11 @@
         final List<ActivityIntentInfo> protectedFilters = mProtectedFilters;
         mProtectedFilters = null;
 
-        final String setupWizardPackage = sPackageManagerInternal.getKnownPackageName(
-                PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM);
+        // expect single setupwizard package
+        final String setupWizardPackage = ArrayUtils.firstOrNull(
+                sPackageManagerInternal.getKnownPackageNames(
+                        PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM));
+
         if (DEBUG_FILTERS && setupWizardPackage == null) {
             Slog.i(TAG, "No setup wizard;"
                     + " All protected intents capped to priority 0");
diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java
new file mode 100644
index 0000000..b0cf525
--- /dev/null
+++ b/services/core/java/com/android/server/pm/InstallSource.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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.pm;
+
+import android.annotation.Nullable;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+/**
+ * Immutable class holding information about where the request to install or update an app
+ * came from.
+ */
+final class InstallSource {
+    private static final InstallSource EMPTY = new InstallSource(null);
+
+    /**
+     * The package that requested the installation, if known.
+     */
+    @Nullable
+    final String initiatingPackageName;
+
+    static InstallSource create(@Nullable String initiatingPackageName) {
+        return initiatingPackageName == null
+                ? EMPTY : new InstallSource(initiatingPackageName.intern());
+    }
+
+    private InstallSource(@Nullable String initiatingPackageName) {
+        this.initiatingPackageName = initiatingPackageName;
+    }
+
+    void dump(IndentingPrintWriter pw) {
+        pw.printPair("installInitiatingPackageName", initiatingPackageName);
+    }
+
+    /**
+     * Return an InstallSource the same as this one except it does not refer to the specified
+     * installer package name.
+     */
+    InstallSource removeInstallerPackage(String packageName) {
+        if (packageName != null && packageName.equals(initiatingPackageName)) {
+            return create(null);
+        }
+        return this;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index a7d4237..eca93bb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -482,15 +482,24 @@
             throw new SecurityException("User restriction prevents installing");
         }
 
+        String requestedInstallerPackageName = params.installerPackageName != null
+                ? params.installerPackageName : installerPackageName;
+
         if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
             params.installFlags |= PackageManager.INSTALL_FROM_ADB;
 
         } else {
+            if (callingUid != Process.SYSTEM_UID) {
+                // The supplied installerPackageName must always belong to the calling app.
+                mAppOps.checkPackage(callingUid, installerPackageName);
+            }
             // Only apps with INSTALL_PACKAGES are allowed to set an installer that is not the
             // caller.
-            if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) !=
-                    PackageManager.PERMISSION_GRANTED) {
-                mAppOps.checkPackage(callingUid, installerPackageName);
+            if (!requestedInstallerPackageName.equals(installerPackageName)) {
+                if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    mAppOps.checkPackage(callingUid, requestedInstallerPackageName);
+                }
             }
 
             params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
@@ -614,11 +623,12 @@
                 stageCid = buildExternalStageCid(sessionId);
             }
         }
+        InstallSource installSource = InstallSource.create(installerPackageName);
         session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                 mInstallThread.getLooper(), mStagingManager, sessionId, userId,
-                installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
-                false, false, null, SessionInfo.INVALID_ID, false, false, false,
-                SessionInfo.STAGED_SESSION_NO_ERROR, "");
+                requestedInstallerPackageName, callingUid, installSource, params, createdMillis,
+                stageDir, stageCid, false, false, false, null, SessionInfo.INVALID_ID,
+                false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");
 
         synchronized (mSessions) {
             mSessions.put(sessionId, session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index b720290..d8bfa7d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -146,6 +146,8 @@
     private static final String ATTR_USER_ID = "userId";
     private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
     private static final String ATTR_INSTALLER_UID = "installerUid";
+    private static final String ATTR_INITIATING_PACKAGE_NAME =
+            "installInitiatingPackageName";
     private static final String ATTR_CREATED_MILLIS = "createdMillis";
     private static final String ATTR_UPDATED_MILLIS = "updatedMillis";
     private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir";
@@ -218,6 +220,10 @@
     @GuardedBy("mLock")
     private int mInstallerUid;
 
+    /** Where this install request came from */
+    @GuardedBy("mLock")
+    private InstallSource mInstallSource;
+
     @GuardedBy("mLock")
     private float mClientProgress = 0;
     @GuardedBy("mLock")
@@ -413,7 +419,8 @@
             Context context, PackageManagerService pm,
             PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager,
             int sessionId, int userId,
-            String installerPackageName, int installerUid, SessionParams params, long createdMillis,
+            String installerPackageName, int installerUid, @NonNull InstallSource installSource,
+            SessionParams params, long createdMillis,
             File stageDir, String stageCid, boolean prepared, boolean committed, boolean sealed,
             @Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
             boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
@@ -430,6 +437,7 @@
         mOriginalInstallerUid = installerUid;
         mInstallerPackageName = installerPackageName;
         mInstallerUid = installerUid;
+        mInstallSource = Preconditions.checkNotNull(installSource);
         this.params = params;
         this.createdMillis = createdMillis;
         this.updatedMillis = createdMillis;
@@ -1225,6 +1233,7 @@
 
             mInstallerPackageName = packageName;
             mInstallerUid = newOwnerAppInfo.uid;
+            mInstallSource = InstallSource.create(packageName);
         }
 
         // Persist the fact that we've sealed ourselves to prevent
@@ -1443,7 +1452,7 @@
 
         mRelinquished = true;
         return new PackageManagerService.ActiveInstallSession(mPackageName, stageDir,
-                localObserver, params, mInstallerPackageName, mInstallerUid, user,
+                localObserver, params, mInstallerPackageName, mInstallerUid, mInstallSource, user,
                 mSigningDetails);
     }
 
@@ -2336,6 +2345,7 @@
         pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid);
         pw.printPair("mInstallerPackageName", mInstallerPackageName);
         pw.printPair("mInstallerUid", mInstallerUid);
+        mInstallSource.dump(pw);
         pw.printPair("createdMillis", createdMillis);
         pw.printPair("updatedMillis", updatedMillis);
         pw.printPair("stageDir", stageDir);
@@ -2416,6 +2426,8 @@
             writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME,
                     mInstallerPackageName);
             writeIntAttribute(out, ATTR_INSTALLER_UID, mInstallerUid);
+            writeStringAttribute(out, ATTR_INITIATING_PACKAGE_NAME,
+                    mInstallSource.initiatingPackageName);
             writeLongAttribute(out, ATTR_CREATED_MILLIS, createdMillis);
             writeLongAttribute(out, ATTR_UPDATED_MILLIS, updatedMillis);
             if (stageDir != null) {
@@ -2521,6 +2533,8 @@
         final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME);
         final int installerUid = readIntAttribute(in, ATTR_INSTALLER_UID, pm.getPackageUid(
                 installerPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId));
+        final String installInitiatingPackageName =
+                readStringAttribute(in, ATTR_INITIATING_PACKAGE_NAME);
         final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS);
         long updatedMillis = readLongAttribute(in, ATTR_UPDATED_MILLIS);
         final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR);
@@ -2612,17 +2626,11 @@
             childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY;
         }
 
+        InstallSource installSource = InstallSource.create(installInitiatingPackageName);
         return new PackageInstallerSession(callback, context, pm, sessionProvider,
                 installerThread, stagingManager, sessionId, userId, installerPackageName,
-                installerUid, params, createdMillis, stageDir, stageCid, prepared, committed,
-                sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
-                stagedSessionErrorCode, stagedSessionErrorMessage);
-    }
-
-    /**
-     * Reads the session ID from a child session tag stored in the provided {@link XmlPullParser}
-     */
-    static int readChildSessionIdFromXml(@NonNull XmlPullParser in) {
-        return readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID);
+                installerUid, installSource, params, createdMillis, stageDir, stageCid,
+                prepared, committed, sealed, childSessionIdsArray, parentSessionId,
+                isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage);
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d675e36..d057aa2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -974,124 +974,9 @@
         @Override public final boolean hasFeature(String feature) {
             return PackageManagerService.this.hasSystemFeature(feature, 0);
         }
-
-        final List<PackageParser.Package> getStaticOverlayPackages(
-                Collection<PackageParser.Package> allPackages, String targetPackageName) {
-            if ("android".equals(targetPackageName)) {
-                // Static RROs targeting to "android", ie framework-res.apk, are already applied by
-                // native AssetManager.
-                return null;
-            }
-
-            List<PackageParser.Package> overlayPackages = null;
-            for (PackageParser.Package p : allPackages) {
-                if (targetPackageName.equals(p.mOverlayTarget) && p.mOverlayIsStatic) {
-                    if (overlayPackages == null) {
-                        overlayPackages = new ArrayList<>();
-                    }
-                    overlayPackages.add(p);
-                }
-            }
-            if (overlayPackages != null) {
-                Comparator<PackageParser.Package> cmp =
-                        Comparator.comparingInt(p -> p.mOverlayPriority);
-                overlayPackages.sort(cmp);
-            }
-            return overlayPackages;
-        }
-
-        final String[] getStaticOverlayPaths(List<PackageParser.Package> overlayPackages,
-                String targetPath) {
-            if (overlayPackages == null || overlayPackages.isEmpty()) {
-                return null;
-            }
-            List<String> overlayPathList = null;
-            for (PackageParser.Package overlayPackage : overlayPackages) {
-                if (targetPath == null) {
-                    if (overlayPathList == null) {
-                        overlayPathList = new ArrayList<>();
-                    }
-                    overlayPathList.add(overlayPackage.baseCodePath);
-                    continue;
-                }
-
-                try {
-                    // Creates idmaps for system to parse correctly the Android manifest of the
-                    // target package.
-                    //
-                    // OverlayManagerService will update each of them with a correct gid from its
-                    // target package app id.
-                    mInstaller.idmap(targetPath, overlayPackage.baseCodePath,
-                            UserHandle.getSharedAppGid(
-                                    UserHandle.getUserGid(UserHandle.USER_SYSTEM)));
-                    if (overlayPathList == null) {
-                        overlayPathList = new ArrayList<>();
-                    }
-                    overlayPathList.add(overlayPackage.baseCodePath);
-                } catch (InstallerException e) {
-                    Slog.e(TAG, "Failed to generate idmap for " + targetPath + " and " +
-                            overlayPackage.baseCodePath);
-                }
-            }
-            return overlayPathList == null ? null : overlayPathList.toArray(new String[0]);
-        }
-
-        String[] getStaticOverlayPaths(String targetPackageName, String targetPath) {
-            List<PackageParser.Package> overlayPackages;
-            synchronized (mInstallLock) {
-                synchronized (mLock) {
-                    overlayPackages = getStaticOverlayPackages(
-                            mPackages.values(), targetPackageName);
-                }
-                // It is safe to keep overlayPackages without holding mPackages because static overlay
-                // packages can't be uninstalled or disabled.
-                return getStaticOverlayPaths(overlayPackages, targetPath);
-            }
-        }
-
-        @Override public final String[] getOverlayApks(String targetPackageName) {
-            return getStaticOverlayPaths(targetPackageName, null);
-        }
-
-        @Override public final String[] getOverlayPaths(String targetPackageName,
-                String targetPath) {
-            return getStaticOverlayPaths(targetPackageName, targetPath);
-        }
-    }
-
-    class ParallelPackageParserCallback extends PackageParserCallback {
-        List<PackageParser.Package> mOverlayPackages = null;
-
-        void findStaticOverlayPackages() {
-            synchronized (mLock) {
-                for (PackageParser.Package p : mPackages.values()) {
-                    if (p.mOverlayIsStatic) {
-                        if (mOverlayPackages == null) {
-                            mOverlayPackages = new ArrayList<>();
-                        }
-                        mOverlayPackages.add(p);
-                    }
-                }
-            }
-        }
-
-        @Override
-        synchronized String[] getStaticOverlayPaths(String targetPackageName, String targetPath) {
-            // We can trust mOverlayPackages without holding mPackages because package uninstall
-            // can't happen while running parallel parsing.
-            // And we can call mInstaller inside getStaticOverlayPaths without holding mInstallLock
-            // because mInstallLock is held before running parallel parsing.
-            // Moreover holding mPackages or mInstallLock on each parsing thread causes dead-lock.
-            return mOverlayPackages == null ? null :
-                    getStaticOverlayPaths(
-                            getStaticOverlayPackages(mOverlayPackages, targetPackageName),
-                            targetPath);
-        }
     }
 
     final PackageParser.Callback mPackageParserCallback = new PackageParserCallback();
-    final ParallelPackageParserCallback mParallelPackageParserCallback =
-            new ParallelPackageParserCallback();
 
     // Currently known shared libraries.
     final ArrayMap<String, LongSparseArray<SharedLibraryInfo>> mSharedLibraries = new ArrayMap<>();
@@ -1603,6 +1488,8 @@
     final @Nullable String mConfiguratorPackage;
     final @Nullable String mAppPredictionServicePackage;
     final @Nullable String mIncidentReportApproverPackage;
+    final @Nullable String[] mTelephonyPackages;
+    final @Nullable String mWifiPackage;
     final @NonNull String mServicesSystemSharedLibraryPackageName;
     final @NonNull String mSharedSystemSharedLibraryPackageName;
 
@@ -1675,7 +1562,8 @@
                     }
                     // Send broadcasts
                     for (int i = 0; i < size; i++) {
-                        sendPackageChangedBroadcast(packages[i], true, components[i], uids[i]);
+                        sendPackageChangedBroadcast(packages[i], true, components[i], uids[i],
+                                null);
                     }
                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                     break;
@@ -2163,7 +2051,7 @@
                     // send broadcast that all consumers of the static shared library have changed
                     sendPackageChangedBroadcast(pkg.packageName, false /*killFlag*/,
                             new ArrayList<>(Collections.singletonList(pkg.packageName)),
-                            pkg.applicationInfo.uid);
+                            pkg.applicationInfo.uid, null);
                 }
             }
 
@@ -2805,8 +2693,6 @@
                         systemScanFlags | partition.scanFlag, 0);
             }
 
-            mParallelPackageParserCallback.findStaticOverlayPackages();
-
             scanDirTracedLI(frameworkDir, systemParseFlags,
                     systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0);
             if (!mPackages.containsKey("android")) {
@@ -3058,6 +2944,8 @@
                     mContext.getString(R.string.config_deviceConfiguratorPackageName);
             mAppPredictionServicePackage = getAppPredictionServicePackageName();
             mIncidentReportApproverPackage = getIncidentReportApproverPackageName();
+            mTelephonyPackages = getTelephonyPackageNames();
+            mWifiPackage = mContext.getString(R.string.config_wifiPackage);
 
             // Now that we know all of the shared libraries, update all clients to have
             // the correct library paths.
@@ -8466,7 +8354,7 @@
         }
         try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
                 mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
-                mParallelPackageParserCallback)) {
+                mPackageParserCallback)) {
             // Submit files for parsing in parallel
             int fileCount = 0;
             for (File file : files) {
@@ -10682,6 +10570,50 @@
         return changedAbiCodePath;
     }
 
+    /**
+     * Sets the enabled state of components configured through {@link SystemConfig}.
+     * This modifies the {@link PackageSetting} object.
+     **/
+    static void configurePackageComponents(PackageParser.Package pkg) {
+        final ArrayMap<String, Boolean> componentsEnabledStates = SystemConfig.getInstance()
+                .getComponentsEnabledStates(pkg.packageName);
+        if (componentsEnabledStates == null) {
+            return;
+        }
+
+        for (int i = pkg.activities.size() - 1; i >= 0; i--) {
+            final PackageParser.Activity component = pkg.activities.get(i);
+            final Boolean enabled = componentsEnabledStates.get(component.className);
+            if (enabled != null) {
+                component.info.enabled = enabled;
+            }
+        }
+
+        for (int i = pkg.receivers.size() - 1; i >= 0; i--) {
+            final PackageParser.Activity component = pkg.receivers.get(i);
+            final Boolean enabled = componentsEnabledStates.get(component.className);
+            if (enabled != null) {
+                component.info.enabled = enabled;
+            }
+        }
+
+        for (int i = pkg.providers.size() - 1; i >= 0; i--) {
+            final PackageParser.Provider component = pkg.providers.get(i);
+            final Boolean enabled = componentsEnabledStates.get(component.className);
+            if (enabled != null) {
+                component.info.enabled = enabled;
+            }
+        }
+
+        for (int i = pkg.services.size() - 1; i >= 0; i--) {
+            final PackageParser.Service component = pkg.services.get(i);
+            final Boolean enabled = componentsEnabledStates.get(component.className);
+            if (enabled != null) {
+                component.info.enabled = enabled;
+            }
+        }
+    }
+
 
     /**
      * Just scans the package without any side effects.
@@ -10849,6 +10781,10 @@
             pkg.applicationInfo.initForUser(UserHandle.USER_SYSTEM);
         }
 
+        if (pkg.isSystem()) {
+            configurePackageComponents(pkg);
+        }
+
         final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
 
         if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
@@ -13979,6 +13915,7 @@
         final IPackageInstallObserver2 observer;
         int installFlags;
         final String installerPackageName;
+        final InstallSource installSource;
         final String volumeUuid;
         private boolean mVerificationCompleted;
         private boolean mEnableRollbackCompleted;
@@ -13995,10 +13932,11 @@
         final long requiredInstalledVersionCode;
 
         InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
-                int installFlags, String installerPackageName, String volumeUuid,
+                int installFlags, String installerPackageName,
+                InstallSource installSource, String volumeUuid,
                 VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride,
                 String[] grantedPermissions, List<String> whitelistedRestrictedPermissions,
-                PackageParser.SigningDetails signingDetails, int installReason,
+                SigningDetails signingDetails, int installReason,
                 long requiredInstalledVersionCode) {
             super(user);
             this.origin = origin;
@@ -14006,6 +13944,7 @@
             this.observer = observer;
             this.installFlags = installFlags;
             this.installerPackageName = installerPackageName;
+            this.installSource = installSource;
             this.volumeUuid = volumeUuid;
             this.verificationInfo = verificationInfo;
             this.packageAbiOverride = packageAbiOverride;
@@ -14037,6 +13976,7 @@
             observer = activeInstallSession.getObserver();
             installFlags = activeInstallSession.getSessionParams().installFlags;
             installerPackageName = activeInstallSession.getInstallerPackageName();
+            installSource = activeInstallSession.getInstallSource();
             volumeUuid = activeInstallSession.getSessionParams().volumeUuid;
             packageAbiOverride = activeInstallSession.getSessionParams().abiOverride;
             grantedRuntimePermissions = activeInstallSession.getSessionParams()
@@ -14519,6 +14459,7 @@
         // Always refers to PackageManager flags only
         final int installFlags;
         final String installerPackageName;
+        final InstallSource installSource;
         final String volumeUuid;
         final UserHandle user;
         final String abiOverride;
@@ -14537,7 +14478,8 @@
         /* nullable */ String[] instructionSets;
 
         InstallArgs(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
-                int installFlags, String installerPackageName, String volumeUuid,
+                int installFlags, String installerPackageName,
+                InstallSource installSource, String volumeUuid,
                 UserHandle user, String[] instructionSets,
                 String abiOverride, String[] installGrantPermissions,
                 List<String> whitelistedRestrictedPermissions,
@@ -14549,6 +14491,7 @@
             this.installFlags = installFlags;
             this.observer = observer;
             this.installerPackageName = installerPackageName;
+            this.installSource = installSource;
             this.volumeUuid = volumeUuid;
             this.user = user;
             this.instructionSets = instructionSets;
@@ -14562,6 +14505,16 @@
             this.mMultiPackageInstallParams = multiPackageInstallParams;
         }
 
+        /** New install */
+        InstallArgs(InstallParams params) {
+            this(params.origin, params.move, params.observer, params.installFlags,
+                    params.installerPackageName, params.installSource, params.volumeUuid,
+                    params.getUser(), null /*instructionSets*/, params.packageAbiOverride,
+                    params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions,
+                    params.traceMethod, params.traceCookie, params.signingDetails,
+                    params.installReason, params.mParentInstallParams);
+        }
+
         abstract int copyApk();
         abstract int doPreInstall(int status);
 
@@ -14642,17 +14595,12 @@
 
         /** New install */
         FileInstallArgs(InstallParams params) {
-            super(params.origin, params.move, params.observer, params.installFlags,
-                    params.installerPackageName, params.volumeUuid,
-                    params.getUser(), null /*instructionSets*/, params.packageAbiOverride,
-                    params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions,
-                    params.traceMethod, params.traceCookie, params.signingDetails,
-                    params.installReason, params.mParentInstallParams);
+            super(params);
         }
 
         /** Existing install */
         FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) {
-            super(OriginInfo.fromNothing(), null, null, 0, null, null, null, instructionSets,
+            super(OriginInfo.fromNothing(), null, null, 0, null, null, null, null, instructionSets,
                     null, null, null, null, 0, PackageParser.SigningDetails.UNKNOWN,
                     PackageManager.INSTALL_REASON_UNKNOWN, null /* parent */);
             this.codeFile = (codePath != null) ? new File(codePath) : null;
@@ -14831,12 +14779,7 @@
 
         /** New install */
         MoveInstallArgs(InstallParams params) {
-            super(params.origin, params.move, params.observer, params.installFlags,
-                    params.installerPackageName, params.volumeUuid,
-                    params.getUser(), null /* instruction sets */, params.packageAbiOverride,
-                    params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions,
-                    params.traceMethod, params.traceCookie, params.signingDetails,
-                    params.installReason, params.mParentInstallParams);
+            super(params);
         }
 
         int copyApk() {
@@ -15095,38 +15038,38 @@
         return disabled;
     }
 
-    private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
-            int[] allUsers, PackageInstalledInfo res, UserHandle user, int installReason) {
+    private void updateSettingsLI(PackageParser.Package newPackage,
+            InstallArgs installArgs, int[] allUsers, PackageInstalledInfo res) {
         // Update the parent package setting
-        updateSettingsInternalLI(newPackage, installerPackageName, allUsers, res.origUsers,
-                res, user, installReason);
+        updateSettingsInternalLI(newPackage, installArgs, allUsers, res);
         // Update the child packages setting
         final int childCount = (newPackage.childPackages != null)
                 ? newPackage.childPackages.size() : 0;
         for (int i = 0; i < childCount; i++) {
             PackageParser.Package childPackage = newPackage.childPackages.get(i);
             PackageInstalledInfo childRes = res.addedChildPackages.get(childPackage.packageName);
-            updateSettingsInternalLI(childPackage, installerPackageName, allUsers,
-                    childRes.origUsers, childRes, user, installReason);
+            updateSettingsInternalLI(childPackage, installArgs, allUsers, childRes);
         }
     }
 
     private void updateSettingsInternalLI(PackageParser.Package pkg,
-            String installerPackageName, int[] allUsers, int[] installedForUsers,
-            PackageInstalledInfo res, UserHandle user, int installReason) {
+            InstallArgs installArgs, int[] allUsers, PackageInstalledInfo res) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
 
         final String pkgName = pkg.packageName;
+        final String installerPackageName = installArgs.installerPackageName;
+        final int[] installedForUsers = res.origUsers;
+        final int installReason = installArgs.installReason;
 
         if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.codePath);
         synchronized (mLock) {
 // NOTE: This changes slightly to include UPDATE_PERMISSIONS_ALL regardless of the size of pkg.permissions
-            mPermissionManager.updatePermissions(pkg.packageName, pkg);
+            mPermissionManager.updatePermissions(pkgName, pkg);
             // For system-bundled packages, we assume that installing an upgraded version
             // of the package implies that the user actually wants to run that new code,
             // so we enable the package.
             PackageSetting ps = mSettings.mPackages.get(pkgName);
-            final int userId = user.getIdentifier();
+            final int userId = installArgs.user.getIdentifier();
             if (ps != null) {
                 if (isSystemApp(pkg)) {
                     if (DEBUG_INSTALL) {
@@ -15162,6 +15105,9 @@
                     ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
                 }
 
+                ps.setInstallSource(installArgs.installSource);
+
+
                 // When replacing an existing package, preserve the original install reason for all
                 // users that had the package installed before.
                 final Set<Integer> previousUserIds = new ArraySet<>();
@@ -15805,8 +15751,7 @@
             }
 
             commitReconciledScanResultLocked(reconciledPkg);
-            updateSettingsLI(pkg, reconciledPkg.installArgs.installerPackageName, request.mAllUsers,
-                    res, reconciledPkg.installArgs.user, reconciledPkg.installArgs.installReason);
+            updateSettingsLI(pkg, reconciledPkg.installArgs, request.mAllUsers, res);
 
             final PackageSetting ps = mSettings.mPackages.get(packageName);
             if (ps != null) {
@@ -15816,8 +15761,7 @@
             final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
             for (int i = 0; i < childCount; i++) {
                 PackageParser.Package childPkg = pkg.childPackages.get(i);
-                PackageInstalledInfo childRes = res.addedChildPackages.get(
-                        childPkg.packageName);
+                PackageInstalledInfo childRes = res.addedChildPackages.get(childPkg.packageName);
                 PackageSetting childPs = mSettings.getPackageLPr(childPkg.packageName);
                 if (childPs != null) {
                     childRes.newUsers = childPs.queryInstalledUsers(
@@ -16064,10 +16008,6 @@
      * will be used to scan and reconcile the package.
      */
     private static class PrepareResult {
-        public final int installReason;
-        public final String volumeUuid;
-        public final String installerPackageName;
-        public final UserHandle user;
         public final boolean replace;
         public final int scanFlags;
         public final int parseFlags;
@@ -16076,24 +16016,16 @@
         public final PackageParser.Package packageToScan;
         public final boolean clearCodeCache;
         public final boolean system;
-        /* The original package name if it was changed during an update, otherwise {@code null}. */
-        @Nullable
-        public final String renamedPackage;
         public final PackageFreezer freezer;
         public final PackageSetting originalPs;
         public final PackageSetting disabledPs;
         public final PackageSetting[] childPackageSettings;
 
-        private PrepareResult(int installReason, String volumeUuid,
-                String installerPackageName, UserHandle user, boolean replace, int scanFlags,
+        private PrepareResult(boolean replace, int scanFlags,
                 int parseFlags, PackageParser.Package existingPackage,
                 PackageParser.Package packageToScan, boolean clearCodeCache, boolean system,
-                String renamedPackage, PackageFreezer freezer, PackageSetting originalPs,
+                PackageFreezer freezer, PackageSetting originalPs,
                 PackageSetting disabledPs, PackageSetting[] childPackageSettings) {
-            this.installReason = installReason;
-            this.volumeUuid = volumeUuid;
-            this.installerPackageName = installerPackageName;
-            this.user = user;
             this.replace = replace;
             this.scanFlags = scanFlags;
             this.parseFlags = parseFlags;
@@ -16101,7 +16033,6 @@
             this.packageToScan = packageToScan;
             this.clearCodeCache = clearCodeCache;
             this.system = system;
-            this.renamedPackage = renamedPackage;
             this.freezer = freezer;
             this.originalPs = originalPs;
             this.disabledPs = disabledPs;
@@ -16141,8 +16072,6 @@
     private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
             throws PrepareFailure {
         final int installFlags = args.installFlags;
-        final String installerPackageName = args.installerPackageName;
-        final String volumeUuid = args.volumeUuid;
         final File tmpPackageFile = new File(args.getCodePath());
         final boolean onExternal = args.volumeUuid != null;
         final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0);
@@ -16567,14 +16496,12 @@
             final PackageParser.Package existingPackage;
             String renamedPackage = null;
             boolean sysPkg = false;
-            String targetVolumeUuid = volumeUuid;
             int targetScanFlags = scanFlags;
             int targetParseFlags = parseFlags;
             final PackageSetting ps;
             final PackageSetting disabledPs;
             final PackageSetting[] childPackages;
             if (replace) {
-                targetVolumeUuid = null;
                 if (pkg.applicationInfo.isStaticSharedLibrary()) {
                     // Static libs have a synthetic package name containing the version
                     // and cannot be updated as an update would get a new package name,
@@ -16826,9 +16753,8 @@
             // we're passing the freezer back to be closed in a later phase of install
             shouldCloseFreezerBeforeReturn = false;
 
-            return new PrepareResult(args.installReason, targetVolumeUuid, installerPackageName,
-                    args.user, replace, targetScanFlags, targetParseFlags, existingPackage, pkg,
-                    replace /* clearCodeCache */, sysPkg, renamedPackage, freezer,
+            return new PrepareResult(replace, targetScanFlags, targetParseFlags,
+                    existingPackage, pkg, replace /* clearCodeCache */, sysPkg, freezer,
                     ps, disabledPs, childPackages);
         } finally {
             if (shouldCloseFreezerBeforeReturn) {
@@ -17509,7 +17435,7 @@
                             continue;
                         }
                         List<VersionedPackage> libClientPackages = getPackagesUsingSharedLibraryLPr(
-                                libraryInfo, 0, currUserId);
+                                libraryInfo, MATCH_KNOWN_PACKAGES, currUserId);
                         if (!ArrayUtils.isEmpty(libClientPackages)) {
                             Slog.w(TAG, "Not removing package " + pkg.manifestPackageName
                                     + " hosting lib " + libraryInfo.getName() + " version "
@@ -19797,6 +19723,16 @@
     }
 
     @Override
+    public String[] getTelephonyPackageNames() {
+        String names = mContext.getString(R.string.config_telephonyPackages);
+        String[] telephonyPackageNames = null;
+        if (!TextUtils.isEmpty(names)) {
+            telephonyPackageNames = names.trim().split(",");
+        }
+        return telephonyPackageNames;
+    }
+
+    @Override
     public void setApplicationEnabledSetting(String appPackageName,
             int newState, int flags, int userId, String callingPackage) {
         if (!mUserManager.exists(userId)) return;
@@ -20039,7 +19975,7 @@
             if (sendNow) {
                 int packageUid = UserHandle.getUid(userId, pkgSetting.appId);
                 sendPackageChangedBroadcast(packageName,
-                        (flags&PackageManager.DONT_KILL_APP) != 0, components, packageUid);
+                        (flags & PackageManager.DONT_KILL_APP) != 0, components, packageUid, null);
             }
         } finally {
             Binder.restoreCallingIdentity(callingId);
@@ -20071,7 +20007,8 @@
     }
 
     private void sendPackageChangedBroadcast(String packageName,
-            boolean killFlag, ArrayList<String> componentNames, int packageUid) {
+            boolean killFlag, ArrayList<String> componentNames, int packageUid,
+            String reason) {
         if (DEBUG_INSTALL)
             Log.v(TAG, "Sending package changed: package=" + packageName + " components="
                     + componentNames);
@@ -20082,6 +20019,9 @@
         extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
         extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
         extras.putInt(Intent.EXTRA_UID, packageUid);
+        if (reason != null) {
+            extras.putString(Intent.EXTRA_REASON, reason);
+        }
         // If this is not reporting a change of the overall package, then only send it
         // to registered receivers.  We don't want to launch a swath of apps for every
         // little component state change.
@@ -20329,6 +20269,34 @@
             }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
         }
 
+        IntentFilter overlayFilter = new IntentFilter(Intent.ACTION_OVERLAY_CHANGED);
+        overlayFilter.addDataScheme("package");
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent == null) {
+                    return;
+                }
+                Uri data = intent.getData();
+                if (data == null) {
+                    return;
+                }
+                String packageName = data.getSchemeSpecificPart();
+                if (packageName == null) {
+                    return;
+                }
+                PackageParser.Package pkg = mPackages.get(packageName);
+                if (pkg == null) {
+                    return;
+                }
+                sendPackageChangedBroadcast(pkg.packageName,
+                        false /* killFlag */,
+                        new ArrayList<>(Collections.singletonList(pkg.packageName)),
+                        pkg.applicationInfo.uid,
+                        Intent.ACTION_OVERLAY_CHANGED);
+            }
+        }, overlayFilter);
+
         mModuleInfoProvider.systemReady();
 
         // Installer service might attempt to install some packages that have been staged for
@@ -21909,6 +21877,7 @@
         final String currentVolumeUuid;
         final File codeFile;
         final String installerPackageName;
+        final InstallSource installSource;
         final String packageAbiOverride;
         final int appId;
         final String seinfo;
@@ -21966,6 +21935,7 @@
             isCurrentLocationExternal = isExternal(pkg);
             codeFile = new File(pkg.codePath);
             installerPackageName = ps.installerPackageName;
+            installSource = ps.installSource;
             packageAbiOverride = ps.cpuAbiOverrideString;
             appId = UserHandle.getAppId(pkg.applicationInfo.uid);
             seinfo = pkg.applicationInfo.seInfo;
@@ -22111,7 +22081,7 @@
         final Message msg = mHandler.obtainMessage(INIT_COPY);
         final OriginInfo origin = OriginInfo.fromExistingFile(codeFile);
         final InstallParams params = new InstallParams(origin, move, installObserver, installFlags,
-                installerPackageName, volumeUuid, null /*verificationInfo*/, user,
+                installerPackageName, installSource, volumeUuid, null /*verificationInfo*/, user,
                 packageAbiOverride, null /*grantedPermissions*/,
                 null /*whitelistedRestrictedPermissions*/, PackageParser.SigningDetails.UNKNOWN,
                 PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.VERSION_CODE_HIGHEST);
@@ -22869,34 +22839,38 @@
         }
 
         @Override
-        public String getKnownPackageName(int knownPackage, int userId) {
+        public @NonNull String[] getKnownPackageNames(int knownPackage, int userId) {
             switch(knownPackage) {
                 case PackageManagerInternal.PACKAGE_BROWSER:
-                    return mPermissionManager.getDefaultBrowser(userId);
+                    return new String[]{mPermissionManager.getDefaultBrowser(userId)};
                 case PackageManagerInternal.PACKAGE_INSTALLER:
-                    return mRequiredInstallerPackage;
+                    return new String[]{mRequiredInstallerPackage};
                 case PackageManagerInternal.PACKAGE_SETUP_WIZARD:
-                    return mSetupWizardPackage;
+                    return new String[]{mSetupWizardPackage};
                 case PackageManagerInternal.PACKAGE_SYSTEM:
-                    return "android";
+                    return new String[]{"android"};
                 case PackageManagerInternal.PACKAGE_VERIFIER:
-                    return mRequiredVerifierPackage;
+                    return new String[]{mRequiredVerifierPackage};
                 case PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER:
-                    return mSystemTextClassifierPackage;
+                    return new String[]{mSystemTextClassifierPackage};
                 case PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER:
-                    return mRequiredPermissionControllerPackage;
+                    return new String[]{mRequiredPermissionControllerPackage};
                 case PackageManagerInternal.PACKAGE_WELLBEING:
-                    return mWellbeingPackage;
+                    return new String[]{mWellbeingPackage};
                 case PackageManagerInternal.PACKAGE_DOCUMENTER:
-                    return mDocumenterPackage;
+                    return new String[]{mDocumenterPackage};
                 case PackageManagerInternal.PACKAGE_CONFIGURATOR:
-                    return mConfiguratorPackage;
+                    return new String[]{mConfiguratorPackage};
                 case PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER:
-                    return mIncidentReportApproverPackage;
+                    return new String[]{mIncidentReportApproverPackage};
                 case PackageManagerInternal.PACKAGE_APP_PREDICTOR:
-                    return mAppPredictionServicePackage;
+                    return new String[]{mAppPredictionServicePackage};
+                case PackageManagerInternal.PACKAGE_TELEPHONY:
+                    return mTelephonyPackages;
+                case PackageManagerInternal.PACKAGE_WIFI:
+                    return new String[]{mWifiPackage};
             }
-            return null;
+            return ArrayUtils.emptyArray(String.class);
         }
 
         @Override
@@ -23994,18 +23968,21 @@
         private final PackageInstaller.SessionParams mSessionParams;
         private final String mInstallerPackageName;
         private final int mInstallerUid;
+        private final InstallSource mInstallSource;
         private final UserHandle mUser;
         private final SigningDetails mSigningDetails;
 
         ActiveInstallSession(String packageName, File stagedDir, IPackageInstallObserver2 observer,
                 PackageInstaller.SessionParams sessionParams, String installerPackageName,
-                int installerUid, UserHandle user, SigningDetails signingDetails) {
+                int installerUid, InstallSource installSource,
+                UserHandle user, SigningDetails signingDetails) {
             mPackageName = packageName;
             mStagedDir = stagedDir;
             mObserver = observer;
             mSessionParams = sessionParams;
             mInstallerPackageName = installerPackageName;
             mInstallerUid = installerUid;
+            mInstallSource = installSource;
             mUser = user;
             mSigningDetails = signingDetails;
         }
@@ -24034,6 +24011,10 @@
             return mInstallerUid;
         }
 
+        public InstallSource getInstallSource() {
+            return mInstallSource;
+        }
+
         public UserHandle getUser() {
             return mUser;
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1c1c947..52a7c6e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -468,7 +468,7 @@
      * @param pckg
      */
     private int displayPackageFilePath(String pckg, int userId) throws RemoteException {
-        PackageInfo info = mInterface.getPackageInfo(pckg, 0, userId);
+        PackageInfo info = mInterface.getPackageInfo(pckg, PackageManager.MATCH_APEX, userId);
         if (info != null && info.applicationInfo != null) {
             final PrintWriter pw = getOutPrintWriter();
             pw.print("package:");
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 4ea8a30..be0621b 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -205,6 +205,11 @@
                     proto.end(splitToken);
                 }
             }
+
+            long sourceToken = proto.start(PackageProto.INSTALL_SOURCE);
+            proto.write(PackageProto.InstallSourceProto.INITIATING_PACKAGE_NAME,
+                    installSource.initiatingPackageName);
+            proto.end(sourceToken);
         }
         writeUsersInfoToProto(proto, PackageProto.USERS);
         proto.end(packageToken);
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 0da6b54..f0857dd 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -20,6 +20,7 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
+import android.annotation.NonNull;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
@@ -125,6 +126,9 @@
     String installerPackageName;
     /** Indicates if the package that installed this app has been uninstalled */
     boolean isOrphaned;
+    /** Information about the initial install of this package. */
+    @NonNull
+    InstallSource installSource;
     /** UUID of {@link VolumeInfo} hosting this app */
     String volumeUuid;
     /** The category of this app, as hinted by the installer */
@@ -148,8 +152,17 @@
                 ? new ArrayList<>(childPackageNames) : null;
         this.usesStaticLibraries = usesStaticLibraries;
         this.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
-        init(codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString,
-                secondaryCpuAbiString, cpuAbiOverrideString, pVersionCode);
+        this.codePath = codePath;
+        this.codePathString = codePath.toString();
+        this.resourcePath = resourcePath;
+        this.resourcePathString = resourcePath.toString();
+        this.legacyNativeLibraryPathString = legacyNativeLibraryPathString;
+        this.primaryCpuAbiString = primaryCpuAbiString;
+        this.secondaryCpuAbiString = secondaryCpuAbiString;
+        this.cpuAbiOverrideString = cpuAbiOverrideString;
+        this.versionCode = pVersionCode;
+        this.signatures = new PackageSignatures();
+        this.installSource = InstallSource.create(null);
     }
 
     /**
@@ -166,21 +179,6 @@
         doCopy(base);
     }
 
-    void init(File codePath, File resourcePath, String legacyNativeLibraryPathString,
-              String primaryCpuAbiString, String secondaryCpuAbiString,
-              String cpuAbiOverrideString, long pVersionCode) {
-        this.codePath = codePath;
-        this.codePathString = codePath.toString();
-        this.resourcePath = resourcePath;
-        this.resourcePathString = resourcePath.toString();
-        this.legacyNativeLibraryPathString = legacyNativeLibraryPathString;
-        this.primaryCpuAbiString = primaryCpuAbiString;
-        this.secondaryCpuAbiString = secondaryCpuAbiString;
-        this.cpuAbiOverrideString = cpuAbiOverrideString;
-        this.versionCode = pVersionCode;
-        this.signatures = new PackageSignatures();
-    }
-
     public void setInstallerPackageName(String packageName) {
         installerPackageName = packageName;
     }
@@ -189,6 +187,21 @@
         return installerPackageName;
     }
 
+    public void setInstallSource(InstallSource installSource) {
+        this.installSource = installSource == null ? InstallSource.create(null) : installSource;
+    }
+
+    void removeInstallerPackage(String packageName) {
+        if (packageName == null) {
+            return;
+        }
+        if (packageName.equals(installerPackageName)) {
+            installerPackageName = null;
+            isOrphaned = true;
+        }
+        installSource = installSource.removeInstallerPackage(packageName);
+    }
+
     public void setVolumeUuid(String volumeUuid) {
         this.volumeUuid = volumeUuid;
     }
@@ -241,6 +254,7 @@
         firstInstallTime = orig.firstInstallTime;
         installPermissionsFixed = orig.installPermissionsFixed;
         installerPackageName = orig.installerPackageName;
+        installSource = orig.installSource;
         isOrphaned = orig.isOrphaned;
         keySetData = orig.keySetData;
         lastUpdateTime = orig.lastUpdateTime;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0db6e79..5e209965 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -554,10 +554,6 @@
         return null;
     }
 
-    void addAppOpPackage(String permName, String packageName) {
-        mPermissions.addAppOpPackage(permName, packageName);
-    }
-
     SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
         SharedUserSetting s = mSharedUsers.get(name);
         if (s != null) {
@@ -1052,13 +1048,7 @@
             return;
         }
         for (int i = 0; i < mPackages.size(); i++) {
-            final PackageSetting ps = mPackages.valueAt(i);
-            final String installerPackageName = ps.getInstallerPackageName();
-            if (installerPackageName != null
-                    && installerPackageName.equals(packageName)) {
-                ps.setInstallerPackageName(null);
-                ps.isOrphaned = true;
-            }
+            mPackages.valueAt(i).removeInstallerPackage(packageName);
         }
         mInstallerPackages.remove(packageName);
     }
@@ -2854,12 +2844,15 @@
         if (pkg.isOrphaned) {
             serializer.attribute(null, "isOrphaned", "true");
         }
+        InstallSource installSource = pkg.installSource;
+        if (installSource.initiatingPackageName != null) {
+            serializer.attribute(null, "installInitiator", installSource.initiatingPackageName);
+        }
         if (pkg.volumeUuid != null) {
             serializer.attribute(null, "volumeUuid", pkg.volumeUuid);
         }
         if (pkg.categoryHint != ApplicationInfo.CATEGORY_UNDEFINED) {
-            serializer.attribute(null, "categoryHint",
-                    Integer.toString(pkg.categoryHint));
+            serializer.attribute(null, "categoryHint", Integer.toString(pkg.categoryHint));
         }
         if (pkg.parentPackageName != null) {
             serializer.attribute(null, "parentPackageName", pkg.parentPackageName);
@@ -2874,8 +2867,7 @@
 
         pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
 
-        writePermissionsLPr(serializer, pkg.getPermissionsState()
-                    .getInstallPermissionStates());
+        writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissionStates());
 
         writeSigningKeySetLPr(serializer, pkg.keySetData);
         writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
@@ -3613,6 +3605,7 @@
         String systemStr = null;
         String installerPackageName = null;
         String isOrphaned = null;
+        String installInitiatingPackageName = null;
         String volumeUuid = null;
         String categoryHintString = null;
         String updateAvailable = null;
@@ -3659,6 +3652,7 @@
             }
             installerPackageName = parser.getAttributeValue(null, "installer");
             isOrphaned = parser.getAttributeValue(null, "isOrphaned");
+            installInitiatingPackageName = parser.getAttributeValue(null, "installInitiator");
             volumeUuid = parser.getAttributeValue(null, "volumeUuid");
             categoryHintString = parser.getAttributeValue(null, "categoryHint");
             if (categoryHintString != null) {
@@ -3815,6 +3809,7 @@
             packageSetting.uidError = "true".equals(uidError);
             packageSetting.installerPackageName = installerPackageName;
             packageSetting.isOrphaned = "true".equals(isOrphaned);
+            packageSetting.installSource = InstallSource.create(installInitiatingPackageName);
             packageSetting.volumeUuid = volumeUuid;
             packageSetting.categoryHint = categoryHint;
             packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr;
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 036d1e8..323c957 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -449,7 +449,14 @@
     }
 
     void dump(PrintWriter pw) {
-        for (int i = 0; i < mWhitelitsedPackagesForUserTypes.size(); i++) {
+        pw.print("Whitelisted packages per user type");
+        final int size = mWhitelitsedPackagesForUserTypes.size();
+        if (size == 0) {
+            pw.println(": N/A");
+            return;
+        }
+        pw.println(" (" + size + " packages)");
+        for (int i = 0; i < size; i++) {
             final String pkgName = mWhitelitsedPackagesForUserTypes.keyAt(i);
             final String whitelistedUserTypes =
                     UserInfo.flagsToString(mWhitelitsedPackagesForUserTypes.valueAt(i));
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 6d22faa..037912a 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -276,6 +276,12 @@
     public boolean isAppPredictor() {
         return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR) != 0;
     }
+    public boolean isTelephony() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_TELEPHONY) != 0;
+    }
+    public boolean isWifi() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_WIFI) != 0;
+    }
 
     public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) {
         if (!origPackageName.equals(sourcePackageName)) {
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 793cdd2..f247037 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -437,17 +437,20 @@
 
         // Installer
         grantSystemFixedPermissionsToSystemPackage(
-                getKnownPackage(PackageManagerInternal.PACKAGE_INSTALLER, userId),
+                ArrayUtils.firstOrNull(getKnownPackages(
+                        PackageManagerInternal.PACKAGE_INSTALLER, userId)),
                 userId, STORAGE_PERMISSIONS);
 
         // Verifier
-        final String verifier = getKnownPackage(PackageManagerInternal.PACKAGE_VERIFIER, userId);
+        final String verifier = ArrayUtils.firstOrNull(getKnownPackages(
+                PackageManagerInternal.PACKAGE_VERIFIER, userId));
         grantSystemFixedPermissionsToSystemPackage(verifier, userId, STORAGE_PERMISSIONS);
         grantPermissionsToSystemPackage(verifier, userId, PHONE_PERMISSIONS, SMS_PERMISSIONS);
 
         // SetupWizard
         grantPermissionsToSystemPackage(
-                getKnownPackage(PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId), userId,
+                ArrayUtils.firstOrNull(getKnownPackages(
+                        PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId)), userId,
                 PHONE_PERMISSIONS, CONTACTS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS,
                 CAMERA_PERMISSIONS);
 
@@ -596,7 +599,8 @@
                 userId, CONTACTS_PERMISSIONS, CALENDAR_PERMISSIONS);
 
         // Browser
-        String browserPackage = getKnownPackage(PackageManagerInternal.PACKAGE_BROWSER, userId);
+        String browserPackage = ArrayUtils.firstOrNull(getKnownPackages(
+                PackageManagerInternal.PACKAGE_BROWSER, userId));
         if (browserPackage == null) {
             browserPackage = getDefaultSystemHandlerActivityPackageForCategory(
                     Intent.CATEGORY_APP_BROWSER, userId);
@@ -761,8 +765,8 @@
         }
     }
 
-    private String getKnownPackage(int knownPkgId, int userId) {
-        return mServiceInternal.getKnownPackageName(knownPkgId, userId);
+    private @NonNull String[] getKnownPackages(int knownPkgId, int userId) {
+        return mServiceInternal.getKnownPackageNames(knownPkgId, userId);
     }
 
     private void grantDefaultPermissionsToDefaultSystemDialerApp(
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 89908f0..5c65752 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -297,7 +297,7 @@
             // Critical; after this call the application should never have the permission
             mPackageManagerInt.writeSettings(false);
             final int appId = UserHandle.getAppId(uid);
-            killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+            mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
         }
         @Override
         public void onInstallPermissionRevoked() {
@@ -1902,7 +1902,7 @@
     private void restoreRuntimePermissions(@NonNull byte[] backup, @NonNull UserHandle user) {
         synchronized (mLock) {
             mHasNoDelayedPermBackup.delete(user.getIdentifier());
-            mPermissionControllerManager.restoreRuntimePermissionBackup(backup, user);
+            mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(backup, user);
         }
     }
 
@@ -1923,7 +1923,7 @@
                 return;
             }
 
-            mPermissionControllerManager.restoreDelayedRuntimePermissionBackup(packageName, user,
+            mPermissionControllerManager.applyStagedRuntimePermissionBackup(packageName, user,
                     mContext.getMainExecutor(), (hasMoreBackup) -> {
                         if (hasMoreBackup) {
                             return;
@@ -3078,8 +3078,9 @@
                 }
             }
         }
-        final String systemPackageName = mPackageManagerInt.getKnownPackageName(
-                PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
+        // expect single system package
+        String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames(
+                PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM));
         final PackageParser.Package systemPackage =
                 mPackageManagerInt.getPackage(systemPackageName);
 
@@ -3195,18 +3196,19 @@
             //                  need a separate flag anymore. Hence we need to check which
             //                  permissions are needed by the permission controller
             if (!allowed && bp.isInstaller()
-                    && (pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
-                            PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM))
-                    || pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+                    && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                            PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM),
+                    pkg.packageName) || ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
                             PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER,
-                            UserHandle.USER_SYSTEM)))) {
+                    UserHandle.USER_SYSTEM), pkg.packageName)) {
                 // If this permission is to be granted to the system installer and
                 // this app is an installer, then it gets the permission.
                 allowed = true;
             }
             if (!allowed && bp.isVerifier()
-                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
-                            PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM))) {
+                    && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                            PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM),
+                    pkg.packageName)) {
                 // If this permission is to be granted to the system verifier and
                 // this app is a verifier, then it gets the permission.
                 allowed = true;
@@ -3222,53 +3224,71 @@
                 allowed = origPermissions.hasInstallPermission(perm);
             }
             if (!allowed && bp.isSetup()
-                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
-                            PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM))) {
+                    && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                            PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM),
+                    pkg.packageName)) {
                 // If this permission is to be granted to the system setup wizard and
                 // this app is a setup wizard, then it gets the permission.
                 allowed = true;
             }
             if (!allowed && bp.isSystemTextClassifier()
-                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+                    && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
                             PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER,
-                            UserHandle.USER_SYSTEM))) {
+                    UserHandle.USER_SYSTEM), pkg.packageName)) {
                 // Special permissions for the system default text classifier.
                 allowed = true;
             }
             if (!allowed && bp.isConfigurator()
-                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
-                    PackageManagerInternal.PACKAGE_CONFIGURATOR,
-                    UserHandle.USER_SYSTEM))) {
+                    && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                            PackageManagerInternal.PACKAGE_CONFIGURATOR,
+                    UserHandle.USER_SYSTEM), pkg.packageName)) {
                 // Special permissions for the device configurator.
                 allowed = true;
             }
             if (!allowed && bp.isWellbeing()
-                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
-                    PackageManagerInternal.PACKAGE_WELLBEING, UserHandle.USER_SYSTEM))) {
+                    && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                            PackageManagerInternal.PACKAGE_WELLBEING, UserHandle.USER_SYSTEM),
+                    pkg.packageName)) {
                 // Special permission granted only to the OEM specified wellbeing app
                 allowed = true;
             }
             if (!allowed && bp.isDocumenter()
-                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
-                            PackageManagerInternal.PACKAGE_DOCUMENTER, UserHandle.USER_SYSTEM))) {
+                    && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                            PackageManagerInternal.PACKAGE_DOCUMENTER, UserHandle.USER_SYSTEM),
+                    pkg.packageName)) {
                 // If this permission is to be granted to the documenter and
                 // this app is the documenter, then it gets the permission.
                 allowed = true;
             }
             if (!allowed && bp.isIncidentReportApprover()
-                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+                    && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
                             PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER,
-                            UserHandle.USER_SYSTEM))) {
+                    UserHandle.USER_SYSTEM), pkg.packageName)) {
                 // If this permission is to be granted to the incident report approver and
                 // this app is the incident report approver, then it gets the permission.
                 allowed = true;
             }
             if (!allowed && bp.isAppPredictor()
-                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
-                        PackageManagerInternal.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM))) {
+                    && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                            PackageManagerInternal.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM),
+                    pkg.packageName)) {
                 // Special permissions for the system app predictor.
                 allowed = true;
             }
+            if (!allowed && bp.isTelephony()
+                    && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                        PackageManagerInternal.PACKAGE_TELEPHONY, UserHandle.USER_SYSTEM),
+                    pkg.packageName)) {
+                // Special permissions for the system telephony apps.
+                allowed = true;
+            }
+            if (!allowed && bp.isWifi()
+                    && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                        PackageManagerInternal.PACKAGE_WIFI, UserHandle.USER_SYSTEM),
+                    pkg.packageName)) {
+                // Special permissions for the system wifi.
+                allowed = true;
+            }
         }
         return allowed;
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c851cc6..2593c38 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -47,8 +47,8 @@
 import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -2189,10 +2189,10 @@
             default:
                 // These are the windows that by default are shown only to the user that created
                 // them. If this needs to be overridden, set
-                // {@link WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS} in
+                // {@link WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS} in
                 // {@link WindowManager.LayoutParams}. Note that permission
                 // {@link android.Manifest.permission.INTERNAL_SYSTEM_WINDOW} is required as well.
-                if ((attrs.privateFlags & PRIVATE_FLAG_SHOW_FOR_ALL_USERS) == 0) {
+                if ((attrs.privateFlags & SYSTEM_FLAG_SHOW_FOR_ALL_USERS) == 0) {
                     return true;
                 }
                 break;
@@ -2446,7 +2446,7 @@
                     com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
             params.privateFlags |=
                     WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
-            params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+            params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
 
             if (!compatInfo.supportsScreen()) {
                 params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 3439d38..65bb2342 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -29,6 +29,7 @@
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -1334,6 +1335,18 @@
     }
 
     @Override
+    public void grantInlineReplyUriPermission(String key, Uri uri) {
+        enforceStatusBarService();
+        int callingUid = Binder.getCallingUid();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.grantInlineReplyUriPermission(key, uri, callingUid);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
             String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
         (new StatusBarShellCommand(this, mContext)).exec(
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 9d9a37c..673366f 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -1430,9 +1430,8 @@
                 continue;
             }
 
-            final ArrayList<ActivityRecord> activities = task.mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 if (r.isActivityTypeHome()
                         && ((userId == UserHandle.USER_ALL) || (r.mUserId == userId))) {
                     return r;
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
index eff0f75..c6b17e2 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
@@ -39,8 +39,13 @@
  * If an activity is successfully started, the launch sequence's state will transition into
  * {@code STARTED} via {@link #onActivityLaunched}. This is a transient state.
  *
- * It must then transition to either {@code CANCELLED} with {@link #onActivityLaunchCancelled}
- * or into {@code FINISHED} with {@link #onActivityLaunchFinished}. These are terminal states.
+ * It must then transition to either {@code CANCELLED} with {@link #onActivityLaunchCancelled},
+ * which is a terminal state or into {@code FINISHED} with {@link #onActivityLaunchFinished}.
+ *
+ * The {@code FINISHED} with {@link #onActivityLaunchFinished} then may transition to
+ * {@code FULLY_DRAWN} with {@link #onReportFullyDrawn}, which is a terminal state.
+ * Note this transition may not happen if the reportFullyDrawn event is not receivied,
+ * in which case {@code FINISHED} is terminal.
  *
  * Note that the {@code ActivityRecordProto} provided as a parameter to some state transitions isn't
  * necessarily the same within a single launch sequence: it is only the top-most activity at the
@@ -51,15 +56,15 @@
  * until a subsequent transition into {@code INTENT_STARTED} initiates a new launch sequence.
  *
  * <pre>
- *        ┌⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┐     ┌⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┐     ╔══════════════════════════╗
- *    ╴╴▶ ⋮ INTENT_STARTED ⋮ ──▶ ⋮     ACTIVITY_LAUNCHED     ⋮ ──▶ ║ ACTIVITY_LAUNCH_FINISHED ║
- *        └⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┘     └⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┘     ╚══════════════════════════╝
- *          :                      :
- *          :                      :
- *          ▼                      ▼
- *        ╔════════════════╗     ╔═══════════════════════════╗
- *        ║ INTENT_FAILED  ║     ║ ACTIVITY_LAUNCH_CANCELLED ║
- *        ╚════════════════╝     ╚═══════════════════════════╝
+ *        ┌⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┐     ┌⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┐     ┌--------------------------┐
+ *    ╴╴▶  INTENT_STARTED    ──▶      ACTIVITY_LAUNCHED        ──▶   ACTIVITY_LAUNCH_FINISHED
+ *        └⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┘     └⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┘     └--------------------------┘
+ *          :                      :                                 :
+ *          :                      :                                 :
+ *          ▼                      ▼                                 ▼
+ *        ╔════════════════╗     ╔═══════════════════════════╗     ╔═══════════════════════════╗
+ *        ║ INTENT_FAILED  ║     ║ ACTIVITY_LAUNCH_CANCELLED ║     ║    REPORT_FULLY_DRAWN     ║
+ *        ╚════════════════╝     ╚═══════════════════════════╝     ╚═══════════════════════════╝
  * </pre>
  */
 public interface ActivityMetricsLaunchObserver {
@@ -111,7 +116,7 @@
      * Multiple calls to this method cannot occur without first terminating the current
      * launch sequence.
      */
-    public void onIntentStarted(@NonNull Intent intent);
+    public void onIntentStarted(@NonNull Intent intent, long timestampNanos);
 
     /**
      * Notifies the observer that the current launch sequence has failed to launch an activity.
@@ -177,6 +182,9 @@
      * drawn for the first time: the top-most activity at the time is what's reported here.
      *
      * @param finalActivity the top-most activity whose windows were first to fully draw
+     * @param timestampNanos the timestamp of ActivityLaunchFinished event in nanoseconds.
+     *        To compute the TotalTime duration, deduct the timestamp {@link #onIntentStarted}
+     *        from {@code timestampNanos}.
      *
      * Multiple calls to this method cannot occur without first terminating the current
      * launch sequence.
@@ -186,5 +194,22 @@
      *          and only the latest activity that was top-most during first-frame drawn
      *          is reported here.
      */
-    public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] finalActivity);
+    public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] finalActivity,
+                                         long timestampNanos);
+
+    /**
+     * Notifies the observer that the application self-reported itself as being fully drawn.
+     *
+     * @param activity the activity that triggers the ReportFullyDrawn event.
+     * @param timestampNanos the timestamp of ReportFullyDrawn event in nanoseconds.
+     *        To compute the duration, deduct the deduct the timestamp {@link #onIntentStarted}
+     *        from {@code timestampNanos}.
+     *
+     * @apiNote The behavior of ReportFullyDrawn mostly depends on the app.
+     *          It is used as an accurate estimate of meanfully app startup time.
+     *          This event may be missing for many apps.
+     */
+    public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
+        long timestampNanos);
+
 }
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index a0a2967..e6c6b12e 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -92,6 +92,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.SomeArgs;
 import com.android.server.LocalServices;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Listens to activity launches, transitions, visibility changes and window drawn callbacks to
@@ -132,8 +133,8 @@
 
     // set to INVALID_START_TIME in reset.
     // set to valid value in notifyActivityLaunching
-    private long mCurrentTransitionStartTime = INVALID_START_TIME;
-    private long mLastTransitionStartTime = INVALID_START_TIME;
+    private long mCurrentTransitionStartTimeNs = INVALID_START_TIME;
+    private long mLastTransitionStartTimeNs = INVALID_START_TIME;
 
     private int mCurrentTransitionDeviceUptime;
     private int mCurrentTransitionDelayMs;
@@ -326,12 +327,12 @@
                                       intent));
         }
 
-        if (mCurrentTransitionStartTime == INVALID_START_TIME) {
+        if (mCurrentTransitionStartTimeNs == INVALID_START_TIME) {
 
-            mCurrentTransitionStartTime = SystemClock.uptimeMillis();
-            mLastTransitionStartTime = mCurrentTransitionStartTime;
+            mCurrentTransitionStartTimeNs = SystemClock.elapsedRealtimeNanos();
+            mLastTransitionStartTimeNs = mCurrentTransitionStartTimeNs;
 
-            launchObserverNotifyIntentStarted(intent);
+            launchObserverNotifyIntentStarted(intent, mCurrentTransitionStartTimeNs);
         }
     }
 
@@ -382,14 +383,15 @@
                 ? launchedActivity.getWindowingMode()
                 : WINDOWING_MODE_UNDEFINED;
         final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(windowingMode);
-        if (mCurrentTransitionStartTime == INVALID_START_TIME) {
+        if (mCurrentTransitionStartTimeNs == INVALID_START_TIME) {
             // No transition is active ignore this launch.
             return;
         }
 
         if (launchedActivity != null && launchedActivity.mDrawn) {
             // Launched activity is already visible. We cannot measure windows drawn delay.
-            reset(true /* abort */, info, "launched activity already visible");
+            reset(true /* abort */, info, "launched activity already visible",
+                0L /* timestampNs */);
             return;
         }
 
@@ -407,7 +409,8 @@
         if ((!isLoggableResultCode(resultCode) || launchedActivity == null || !processSwitch
                 || windowingMode == WINDOWING_MODE_UNDEFINED) && !otherWindowModesLaunching) {
             // Failed to launch or it was not a process switch, so we don't care about the timing.
-            reset(true /* abort */, info, "failed to launch or not a process switch");
+            reset(true /* abort */, info, "failed to launch or not a process switch",
+                0L /* timestampNs */);
             return;
         } else if (otherWindowModesLaunching) {
             // Don't log this windowing mode but continue with the other windowing modes.
@@ -441,19 +444,20 @@
      * Notifies the tracker that all windows of the app have been drawn.
      */
     WindowingModeTransitionInfoSnapshot notifyWindowsDrawn(@WindowingMode int windowingMode,
-                                                           long timestamp) {
+                                                           long timestampNs) {
         if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn windowingMode=" + windowingMode);
 
         final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(windowingMode);
         if (info == null || info.loggedWindowsDrawn) {
             return null;
         }
-        info.windowsDrawnDelayMs = calculateDelay(timestamp);
+        info.windowsDrawnDelayMs = calculateDelay(timestampNs);
         info.loggedWindowsDrawn = true;
         final WindowingModeTransitionInfoSnapshot infoSnapshot =
                 new WindowingModeTransitionInfoSnapshot(info);
         if (allWindowsDrawn() && mLoggedTransitionStarting) {
-            reset(false /* abort */, info, "notifyWindowsDrawn - all windows drawn");
+            reset(false /* abort */, info, "notifyWindowsDrawn - all windows drawn",
+                timestampNs /* timestampNs */);
         }
         return infoSnapshot;
     }
@@ -476,7 +480,7 @@
      * @param windowingModeToReason A map from windowing mode to a reason integer, which must be on
      *                              of ActivityTaskManagerInternal.APP_TRANSITION_* reasons.
      */
-    void notifyTransitionStarting(SparseIntArray windowingModeToReason, long timestamp) {
+    void notifyTransitionStarting(SparseIntArray windowingModeToReason, long timestampNs) {
         if (!isAnyTransitionActive() || mLoggedTransitionStarting) {
             // Ignore calls to this made after a reset and prior to notifyActivityLaunching.
 
@@ -484,7 +488,7 @@
             return;
         }
         if (DEBUG_METRICS) Slog.i(TAG, "notifyTransitionStarting");
-        mCurrentTransitionDelayMs = calculateDelay(timestamp);
+        mCurrentTransitionDelayMs = calculateDelay(timestampNs);
         mLoggedTransitionStarting = true;
 
         WindowingModeTransitionInfo foundInfo = null;
@@ -501,7 +505,8 @@
         if (allWindowsDrawn()) {
             // abort metrics collection if we cannot find a matching transition.
             final boolean abortMetrics = foundInfo == null;
-            reset(abortMetrics, foundInfo, "notifyTransitionStarting - all windows drawn");
+            reset(abortMetrics, foundInfo, "notifyTransitionStarting - all windows drawn",
+                timestampNs /* timestampNs */);
         }
     }
 
@@ -527,8 +532,8 @@
     }
 
     private boolean hasVisibleNonFinishingActivity(TaskRecord t) {
-        for (int i = t.mActivities.size() - 1; i >= 0; --i) {
-            final ActivityRecord r = t.mActivities.get(i);
+        for (int i = t.getChildCount() - 1; i >= 0; --i) {
+            final ActivityRecord r = t.getChildAt(i);
             if (r.visible && !r.finishing) {
                 return true;
             }
@@ -567,7 +572,8 @@
             logAppTransitionCancel(info);
             mWindowingModeTransitionInfo.remove(r.getWindowingMode());
             if (mWindowingModeTransitionInfo.size() == 0) {
-                reset(true /* abort */, info, "notifyVisibilityChanged to invisible");
+                reset(true /* abort */, info, "notifyVisibilityChanged to invisible",
+                    0L /* timestampNs */);
             }
         }
     }
@@ -598,12 +604,16 @@
     }
 
     private boolean isAnyTransitionActive() {
-        return mCurrentTransitionStartTime != INVALID_START_TIME
+        return mCurrentTransitionStartTimeNs != INVALID_START_TIME
                 && mWindowingModeTransitionInfo.size() > 0;
     }
 
-    private void reset(boolean abort, WindowingModeTransitionInfo info, String cause) {
-        if (DEBUG_METRICS) Slog.i(TAG, "reset abort=" + abort + ",cause=" + cause);
+    private void reset(boolean abort, WindowingModeTransitionInfo info, String cause,
+        long timestampNs) {
+        if (DEBUG_METRICS) {
+            Slog.i(TAG,
+                "reset abort=" + abort + ",cause=" + cause + ",timestamp=" + timestampNs);
+        }
         if (!abort && isAnyTransitionActive()) {
             logAppTransitionMultiEvents();
         }
@@ -615,13 +625,13 @@
             if (abort) {
                 launchObserverNotifyActivityLaunchCancelled(info);
             } else {
-                launchObserverNotifyActivityLaunchFinished(info);
+                launchObserverNotifyActivityLaunchFinished(info, timestampNs);
             }
         } else {
             launchObserverNotifyIntentFailed();
         }
 
-        mCurrentTransitionStartTime = INVALID_START_TIME;
+        mCurrentTransitionStartTimeNs = INVALID_START_TIME;
         mCurrentTransitionDelayMs = INVALID_DELAY;
         mLoggedTransitionStarting = false;
         mWindowingModeTransitionInfo.clear();
@@ -629,12 +639,14 @@
 
     private int calculateCurrentDelay() {
         // Shouldn't take more than 25 days to launch an app, so int is fine here.
-        return (int) (SystemClock.uptimeMillis() - mCurrentTransitionStartTime);
+        return (int) TimeUnit.NANOSECONDS
+            .toMillis(SystemClock.elapsedRealtimeNanos() - mCurrentTransitionStartTimeNs);
     }
 
-    private int calculateDelay(long timestamp) {
+    private int calculateDelay(long timestampNs) {
         // Shouldn't take more than 25 days to launch an app, so int is fine here.
-        return (int) (timestamp - mCurrentTransitionStartTime);
+        return (int) TimeUnit.NANOSECONDS.toMillis(timestampNs -
+            mCurrentTransitionStartTimeNs);
     }
 
     private void logAppTransitionCancel(WindowingModeTransitionInfo info) {
@@ -679,7 +691,7 @@
             // Take a snapshot of the transition info before sending it to the handler for logging.
             // This will avoid any races with other operations that modify the ActivityRecord.
             final WindowingModeTransitionInfoSnapshot infoSnapshot =
-                    new WindowingModeTransitionInfoSnapshot(info);
+                     new WindowingModeTransitionInfoSnapshot(info);
             final int currentTransitionDeviceUptime = mCurrentTransitionDeviceUptime;
             final int currentTransitionDelayMs = mCurrentTransitionDelayMs;
             BackgroundThread.getHandler().post(() -> logAppTransition(
@@ -811,7 +823,9 @@
         final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
         builder.setPackageName(r.packageName);
         builder.addTaggedData(FIELD_CLASS_NAME, r.info.name);
-        long startupTimeMs = SystemClock.uptimeMillis() - mLastTransitionStartTime;
+        long currentTimestampNs = SystemClock.elapsedRealtimeNanos();
+        long startupTimeMs =
+            TimeUnit.NANOSECONDS.toMillis(currentTimestampNs - mLastTransitionStartTimeNs);
         builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS, startupTimeMs);
         builder.setType(restoredFromBundle
                 ? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
@@ -837,6 +851,10 @@
         final WindowingModeTransitionInfoSnapshot infoSnapshot =
                 new WindowingModeTransitionInfoSnapshot(info, r, (int) startupTimeMs);
         BackgroundThread.getHandler().post(() -> logAppFullyDrawn(infoSnapshot));
+
+        // Notify reportFullyDrawn event.
+        launchObserverNotifyReportFullyDrawn(r, currentTimestampNs);
+
         return infoSnapshot;
     }
 
@@ -1006,12 +1024,12 @@
     }
 
     /** Notify the {@link ActivityMetricsLaunchObserver} that a new launch sequence has begun. */
-    private void launchObserverNotifyIntentStarted(Intent intent) {
+    private void launchObserverNotifyIntentStarted(Intent intent, long timestampNs) {
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                 "MetricsLogger:launchObserverNotifyIntentStarted");
 
         // Beginning a launch is timing sensitive and so should be observed as soon as possible.
-        mLaunchObserver.onIntentStarted(intent);
+        mLaunchObserver.onIntentStarted(intent, timestampNs);
 
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -1049,6 +1067,16 @@
     }
 
     /**
+     * Notifies the {@link ActivityMetricsLaunchObserver} the reportFullDrawn event.
+     */
+    private void launchObserverNotifyReportFullyDrawn(ActivityRecord r, long timestampNs) {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+            "MetricsLogger:launchObserverNotifyReportFullyDrawn");
+        mLaunchObserver.onReportFullyDrawn(convertActivityRecordToProto(r), timestampNs);
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    /**
      * Notify the {@link ActivityMetricsLaunchObserver} that the current launch sequence is
      * cancelled.
      */
@@ -1068,12 +1096,14 @@
      * Notify the {@link ActivityMetricsLaunchObserver} that the current launch sequence's activity
      * has fully finished (successfully).
      */
-    private void launchObserverNotifyActivityLaunchFinished(WindowingModeTransitionInfo info) {
+    private void launchObserverNotifyActivityLaunchFinished(WindowingModeTransitionInfo info,
+        long timestampNs) {
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                 "MetricsLogger:launchObserverNotifyActivityLaunchFinished");
 
-        mLaunchObserver.onActivityLaunchFinished(
-                convertActivityRecordToProto(info.launchedActivity));
+        mLaunchObserver
+            .onActivityLaunchFinished(convertActivityRecordToProto(info.launchedActivity),
+                timestampNs);
 
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4f52d9d..fb4de01 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -144,10 +144,10 @@
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutLocked;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
-import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutLocked;
 import static com.android.server.wm.TaskPersister.DEBUG;
 import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -318,6 +318,7 @@
     ArrayList<ResultInfo> results; // pending ActivityResult objs we have received
     HashSet<WeakReference<PendingIntentRecord>> pendingResults; // all pending intents for this act
     ArrayList<ReferrerIntent> newIntents; // any pending new intents for single-top mode
+    Intent mLastNewIntent;  // the last new intent we delivered to client
     ActivityOptions pendingOptions; // most recently given options
     ActivityOptions returningOptions; // options that are coming back via convertToTranslucent
     AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity
@@ -1253,7 +1254,7 @@
 
     boolean setOccludesParent(boolean occludesParent) {
         final boolean changed = super.setOccludesParent(occludesParent);
-        if (changed) {
+        if (changed && task != null) {
             if (!occludesParent) {
                 getActivityStack().convertActivityToTranslucent(this);
             }
@@ -1577,12 +1578,12 @@
                     task.mTaskId, shortComponentName, reason);
             final ArrayList<ActivityRecord> activities = task.mActivities;
             final int index = activities.indexOf(this);
-            if (index < (activities.size() - 1)) {
+            if (index < (task.getChildCount() - 1)) {
                 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
                     // If the caller asked that this activity (and all above it)
                     // be cleared when the task is reset, don't lose that information,
                     // but propagate it up to the next activity.
-                    final ActivityRecord next = activities.get(index + 1);
+                    final ActivityRecord next = task.getChildAt(index + 1);
                     next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
                 }
             }
@@ -2004,10 +2005,6 @@
         if (stopped) {
             clearOptionsLocked();
         }
-
-        if (mAtmService != null) {
-            mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
-        }
     }
 
     /**
@@ -2788,12 +2785,12 @@
         if (positionInTask == -1) {
             throw new IllegalStateException("Activity not found in its task");
         }
-        if (positionInTask == task.mActivities.size() - 1) {
+        if (positionInTask == task.getChildCount() - 1) {
             // It's the topmost activity in the task - should become resumed now
             return true;
         }
         // Check if activity above is finishing now and this one becomes the topmost in task.
-        final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1);
+        final ActivityRecord activityAbove = task.getChildAt(positionInTask + 1);
         if (activityAbove.finishing && results == null) {
             // We will only allow making active if activity above wasn't launched for result.
             // Otherwise it will cause this activity to resume before getting result.
@@ -2844,11 +2841,14 @@
         }
         idle = false;
         results = null;
+        if (newIntents != null && newIntents.size() > 0) {
+            mLastNewIntent = newIntents.get(newIntents.size() - 1);
+        }
         newIntents = null;
         stopped = false;
 
         if (isActivityTypeHome()) {
-            mStackSupervisor.updateHomeProcess(task.mActivities.get(0).app);
+            mStackSupervisor.updateHomeProcess(task.getChildAt(0).app);
         }
 
         if (nowVisible) {
@@ -3102,7 +3102,7 @@
 
     void reportFullyDrawnLocked(boolean restoredFromBundle) {
         final WindowingModeTransitionInfoSnapshot info = mStackSupervisor
-                .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
+            .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
         if (info != null) {
             mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
                     info.windowsFullyDrawnDelayMs, info.getLaunchState());
@@ -3110,13 +3110,13 @@
     }
 
     /** Called when the windows associated app window container are drawn. */
-    void onWindowsDrawn(boolean drawn, long timestamp) {
+    void onWindowsDrawn(boolean drawn, long timestampNs) {
         mDrawn = drawn;
         if (!drawn) {
             return;
         }
         final WindowingModeTransitionInfoSnapshot info = mStackSupervisor
-                .getActivityMetricsLogger().notifyWindowsDrawn(getWindowingMode(), timestamp);
+            .getActivityMetricsLogger().notifyWindowsDrawn(getWindowingMode(), timestampNs);
         final int windowsDrawnDelayMs = info != null ? info.windowsDrawnDelayMs : INVALID_DELAY;
         final @LaunchState int launchState = info != null ? info.getLaunchState() : -1;
         mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
@@ -3542,7 +3542,7 @@
             super.resolveOverrideConfiguration(newParentConfiguration);
             // If the activity has override bounds, the relative configuration (e.g. screen size,
             // layout) needs to be resolved according to the bounds.
-            if (!matchParentBounds()) {
+            if (task != null && !matchParentBounds()) {
                 task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
                         newParentConfiguration);
             }
@@ -4198,12 +4198,19 @@
             return true;
         }
 
-        // Restrict task snapshot starting window to launcher start, or there is no intent at all
-        // (eg. task being brought to front). If the intent is something else, likely the app is
-        // going to show some specific page or view, instead of what's left last time.
+        // Restrict task snapshot starting window to launcher start, or is same as the last
+        // delivered intent, or there is no intent at all (eg. task being brought to front). If
+        // the intent is something else, likely the app is going to show some specific page or
+        // view, instead of what's left last time.
         for (int i = newIntents.size() - 1; i >= 0; i--) {
             final Intent intent = newIntents.get(i);
-            if (intent != null && !ActivityRecord.isMainIntent(intent)) {
+            if (intent == null || ActivityRecord.isMainIntent(intent)) {
+                continue;
+            }
+
+            final boolean sameIntent = mLastNewIntent != null ? mLastNewIntent.filterEquals(intent)
+                    : this.intent.filterEquals(intent);
+            if (!sameIntent || intent.getExtras() != null) {
                 return false;
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 5959254..41c1e4e 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -33,7 +33,7 @@
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
 import static android.view.Display.INVALID_DISPLAY;
@@ -488,7 +488,7 @@
     int numActivities() {
         int count = 0;
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            count += mTaskHistory.get(taskNdx).mActivities.size();
+            count += mTaskHistory.get(taskNdx).getChildCount();
         }
         return count;
     }
@@ -1077,9 +1077,8 @@
     ActivityRecord topRunningNonOverlayTaskActivity() {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
-            final ArrayList<ActivityRecord> activities = task.mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 if (!r.finishing && !r.mTaskOverlay) {
                     return r;
                 }
@@ -1091,9 +1090,8 @@
     ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
-            final ArrayList<ActivityRecord> activities = task.mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                ActivityRecord r = activities.get(activityNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 if (!r.finishing && !r.delayedResume && r != notTop && r.okToShowLocked()) {
                     return r;
                 }
@@ -1117,9 +1115,8 @@
             if (task.mTaskId == taskId) {
                 continue;
             }
-            ArrayList<ActivityRecord> activities = task.mActivities;
-            for (int i = activities.size() - 1; i >= 0; --i) {
-                final ActivityRecord r = activities.get(i);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 // Note: the taskId check depends on real taskId fields being non-zero
                 if (!r.finishing && (token != r.appToken) && r.okToShowLocked()) {
                     return r;
@@ -1431,10 +1428,8 @@
 
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
-            final ArrayList<ActivityRecord> activities = task.mActivities;
-
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                ActivityRecord r = activities.get(activityNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 if (!r.okToShowLocked()) {
                     continue;
                 }
@@ -1500,9 +1495,10 @@
     void awakeFromSleepingLocked() {
         // Ensure activities are no longer sleeping.
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                activities.get(activityNdx).setSleeping(false);
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
+                r.setSleeping(false);
             }
         }
         if (mPausingActivity != null) {
@@ -1516,9 +1512,9 @@
         final int userId = UserHandle.getUserId(aInfo.uid);
 
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            final List<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord ar = activities.get(activityNdx);
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord ar = task.getChildAt(activityNdx);
 
                 if ((userId == ar.mUserId) && packageName.equals(ar.packageName)) {
                     ar.updateApplicationInfo(aInfo);
@@ -1594,9 +1590,9 @@
         // Make sure any paused or stopped but visible activities are now sleeping.
         // This ensures that the activity's onStop() is called.
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 if (r.isState(STARTED, STOPPING, STOPPED, PAUSED, PAUSING)) {
                     r.setSleeping(true);
                 }
@@ -1880,9 +1876,8 @@
         }
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
-            final ArrayList<ActivityRecord> activities = task.mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
 
                 if (r.finishing) {
                     // We don't factor in finishing activities when determining translucency since
@@ -2113,9 +2108,8 @@
                     && top != null && !top.mLaunchTaskBehind;
             for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
                 final TaskRecord task = mTaskHistory.get(taskNdx);
-                final ArrayList<ActivityRecord> activities = task.mActivities;
-                for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                    final ActivityRecord r = activities.get(activityNdx);
+                for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                    final ActivityRecord r = task.getChildAt(activityNdx);
                     final boolean isTop = r == top;
                     if (aboveTop && !isTop) {
                         continue;
@@ -2363,9 +2357,8 @@
     void clearOtherAppTimeTrackers(AppTimeTracker except) {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
-            final ArrayList<ActivityRecord> activities = task.mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 if ( r.appTimeTracker != except) {
                     r.appTimeTracker = null;
                 }
@@ -2423,9 +2416,9 @@
         }
 
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 if (aboveTop) {
                     if (r == topActivity) {
                         aboveTop = false;
@@ -3210,14 +3203,13 @@
 
         // We only do this for activities that are not the root of the task (since if we finish
         // the root, we may no longer have the task!).
-        final ArrayList<ActivityRecord> activities = task.mActivities;
-        final int numActivities = activities.size();
+        final int numActivities = task.getChildCount();
         int lastActivityNdx = task.findRootIndex(true /* effectiveRoot */);
         if (lastActivityNdx == -1) {
             lastActivityNdx = 0;
         }
         for (int i = numActivities - 1; i > lastActivityNdx; --i) {
-            ActivityRecord target = activities.get(i);
+            ActivityRecord target = task.getChildAt(i);
             // TODO: Why is this needed? Looks like we're breaking the loop before we reach the root
             if (target.isRootOfTask()) break;
 
@@ -3257,7 +3249,7 @@
                 final TaskRecord targetTask;
                 final ActivityRecord bottom =
                         !mTaskHistory.isEmpty() && !mTaskHistory.get(0).mActivities.isEmpty() ?
-                                mTaskHistory.get(0).mActivities.get(0) : null;
+                                mTaskHistory.get(0).getChildAt(0) : null;
                 if (bottom != null && target.taskAffinity.equals(bottom.getTaskRecord().affinity)) {
                     // If the activity currently at the bottom has the
                     // same task affinity as the one we are moving,
@@ -3279,7 +3271,7 @@
                 boolean noOptions = canMoveOptions;
                 final int start = replyChainEnd < 0 ? i : replyChainEnd;
                 for (int srcPos = start; srcPos >= i; --srcPos) {
-                    final ActivityRecord p = activities.get(srcPos);
+                    final ActivityRecord p = task.getChildAt(srcPos);
                     if (p.finishing) {
                         continue;
                     }
@@ -3312,7 +3304,7 @@
                     // In this case, we want to finish this activity
                     // and everything above it, so be sneaky and pretend
                     // like these are all in the reply chain.
-                    end = activities.size() - 1;
+                    end = task.getChildCount() - 1;
                 } else if (replyChainEnd < 0) {
                     end = i;
                 } else {
@@ -3320,7 +3312,7 @@
                 }
                 boolean noOptions = canMoveOptions;
                 for (int srcPos = i; srcPos <= end; srcPos++) {
-                    ActivityRecord p = activities.get(srcPos);
+                    ActivityRecord p = task.getChildAt(srcPos);
                     if (p.finishing) {
                         continue;
                     }
@@ -3389,8 +3381,7 @@
         int replyChainEnd = -1;
         final String taskAffinity = task.affinity;
 
-        final ArrayList<ActivityRecord> activities = affinityTask.mActivities;
-        final int numActivities = activities.size();
+        final int numActivities = task.getChildCount();
 
         // Do not operate on or below the effective root Activity.
         int lastActivityNdx = affinityTask.findRootIndex(true /* effectiveRoot */);
@@ -3398,7 +3389,7 @@
             lastActivityNdx = 0;
         }
         for (int i = numActivities - 1; i > lastActivityNdx; --i) {
-            ActivityRecord target = activities.get(i);
+            ActivityRecord target = task.getChildAt(i);
             // TODO: Why is this needed? Looks like we're breaking the loop before we reach the root
             if (target.isRootOfTask()) break;
 
@@ -3435,7 +3426,7 @@
                     if (DEBUG_TASKS) Slog.v(TAG_TASKS,
                             "Finishing task at index " + start + " to " + i);
                     for (int srcPos = start; srcPos >= i; --srcPos) {
-                        final ActivityRecord p = activities.get(srcPos);
+                        final ActivityRecord p = task.getChildAt(srcPos);
                         if (p.finishing) {
                             continue;
                         }
@@ -3443,7 +3434,7 @@
                     }
                 } else {
                     if (taskInsertionPoint < 0) {
-                        taskInsertionPoint = task.mActivities.size();
+                        taskInsertionPoint = task.getChildCount();
 
                     }
 
@@ -3452,7 +3443,7 @@
                             "Reparenting from task=" + affinityTask + ":" + start + "-" + i
                             + " to task=" + task + ":" + taskInsertionPoint);
                     for (int srcPos = start; srcPos >= i; --srcPos) {
-                        final ActivityRecord p = activities.get(srcPos);
+                        final ActivityRecord p = task.getChildAt(srcPos);
                         p.reparent(task, taskInsertionPoint, "resetAffinityTaskIfNeededLocked");
 
                         if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE,
@@ -3589,9 +3580,9 @@
     /** Finish all activities that were started for result from the specified activity. */
     final void finishSubActivityLocked(ActivityRecord self, String resultWho, int requestCode) {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                ActivityRecord r = activities.get(activityNdx);
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 if (r.resultTo == self && r.requestCode == requestCode) {
                     if ((r.resultWho == null && resultWho == null) ||
                         (r.resultWho != null && r.resultWho.equals(resultWho))) {
@@ -3637,11 +3628,11 @@
                 if (taskNdx < 0) {
                     break;
                 }
-                activityNdx = mTaskHistory.get(taskNdx).mActivities.size() - 1;
+                activityNdx = mTaskHistory.get(taskNdx).getChildCount() - 1;
             } while (activityNdx < 0);
         }
         if (activityNdx >= 0) {
-            r = mTaskHistory.get(taskNdx).mActivities.get(activityNdx);
+            r = mTaskHistory.get(taskNdx).getChildAt(activityNdx);
             if (r.isState(STARTED, RESUMED, PAUSING, PAUSED)) {
                 if (!r.isActivityTypeHome() || mService.mHomeProcess != r.app) {
                     Slog.w(TAG, "  Force finishing activity "
@@ -3659,8 +3650,8 @@
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             TaskRecord tr = mTaskHistory.get(taskNdx);
             if (tr.voiceSession != null && tr.voiceSession.asBinder() == sessionBinder) {
-                for (int activityNdx = tr.mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
-                    ActivityRecord r = tr.mActivities.get(activityNdx);
+                for (int activityNdx = tr.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                    ActivityRecord r = tr.getChildAt(activityNdx);
                     if (!r.finishing) {
                         r.finishIfPossible("finish-voice", false /* oomAdj */);
                         didOne = true;
@@ -3668,8 +3659,8 @@
                 }
             } else {
                 // Check if any of the activities are using voice
-                for (int activityNdx = tr.mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
-                    ActivityRecord r = tr.mActivities.get(activityNdx);
+                for (int activityNdx = tr.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                    ActivityRecord r = tr.getChildAt(activityNdx);
                     if (r.voiceSession != null && r.voiceSession.asBinder() == sessionBinder) {
                         // Inform of cancellation
                         r.clearVoiceSessionLocked();
@@ -3695,9 +3686,9 @@
     void finishAllActivitiesImmediately() {
         boolean noActivitiesInStack = true;
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 noActivitiesInStack = false;
                 Slog.d(TAG, "finishAllActivitiesImmediatelyLocked: finishing " + r);
                 r.destroyIfPossible("finishAllActivitiesImmediatelyLocked");
@@ -3765,12 +3756,12 @@
             return false;
         }
         int finishTo = start - 1;
-        ActivityRecord parent = finishTo < 0 ? null : activities.get(finishTo);
+        ActivityRecord parent = finishTo < 0 ? null : task.getChildAt(finishTo);
         boolean foundParentInTask = false;
         final ComponentName dest = destIntent.getComponent();
         if (start > 0 && dest != null) {
             for (int i = finishTo; i >= 0; i--) {
-                ActivityRecord r = activities.get(i);
+                ActivityRecord r = task.getChildAt(i);
                 if (r.info.packageName.equals(dest.getPackageName()) &&
                         r.info.name.equals(dest.getClassName())) {
                     finishTo = i;
@@ -3931,9 +3922,9 @@
         boolean lastIsOpaque = false;
         boolean activityRemoved = false;
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 if (r.finishing) {
                     continue;
                 }
@@ -3978,15 +3969,14 @@
             }
             if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Looking for activities to release in " + task);
             int curNum = 0;
-            final ArrayList<ActivityRecord> activities = task.mActivities;
-            for (int actNdx = 0; actNdx < activities.size(); actNdx++) {
-                final ActivityRecord activity = activities.get(actNdx);
+            for (int actNdx = 0; actNdx < task.getChildCount(); actNdx++) {
+                final ActivityRecord activity = task.getChildAt(actNdx);
                 if (activity.app == app && activity.isDestroyable()) {
                     if (DEBUG_RELEASE) Slog.v(TAG_RELEASE, "Destroying " + activity
                             + " in state " + activity.getState() + " resumed=" + mResumedActivity
                             + " pausing=" + mPausingActivity + " for reason " + reason);
                     activity.destroyImmediately(true /* removeFromApp */, reason);
-                    if (activities.get(actNdx) != activity) {
+                    if (task.getChildAt(actNdx) != activity) {
                         // Was removed from list, back up so we don't miss the next one.
                         actNdx--;
                     }
@@ -4170,8 +4160,8 @@
 
         if (timeTracker != null) {
             // The caller wants a time tracker associated with this task.
-            for (int i = tr.mActivities.size() - 1; i >= 0; i--) {
-                tr.mActivities.get(i).appTimeTracker = timeTracker;
+            for (int i = tr.getChildCount() - 1; i >= 0; i--) {
+                tr.getChildAt(i).appTimeTracker = timeTracker;
             }
         }
 
@@ -4352,7 +4342,7 @@
             return;
         }
 
-        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "stack.resize_" + mStackId);
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "stack.resize_" + mStackId);
         mService.deferWindowLayout();
         try {
             // Update override configurations of all tasks in the stack.
@@ -4378,7 +4368,7 @@
             }
         } finally {
             mService.continueWindowLayout();
-            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
 
@@ -4425,9 +4415,9 @@
 
     boolean willActivityBeVisibleLocked(IBinder token) {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 if (r.appToken == token) {
                     return true;
                 }
@@ -4447,9 +4437,9 @@
 
     void closeSystemDialogsLocked() {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) {
                     r.finishIfPossible("close-sys", true /* oomAdj */);
                 }
@@ -4556,10 +4546,10 @@
         final int top = mTaskHistory.size() - 1;
         if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Performing unhandledBack(): top activity at " + top);
         if (top >= 0) {
-            final ArrayList<ActivityRecord> activities = mTaskHistory.get(top).mActivities;
-            int activityTop = activities.size() - 1;
+            final TaskRecord task = mTaskHistory.get(top);
+            int activityTop = task.getChildCount() - 1;
             if (activityTop >= 0) {
-                activities.get(activityTop).finishIfPossible("unhandled-back", true /* oomAdj */);
+                task.getChildAt(activityTop).finishIfPossible("unhandled-back", true /* oomAdj */);
             }
         }
     }
@@ -4585,9 +4575,9 @@
 
     void handleAppCrash(WindowProcessController app) {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = task.getChildAt(activityNdx);
                 if (r.app == app) {
                     Slog.w(TAG, "  Force finishing activity "
                             + r.intent.getComponent().flattenToShortString());
@@ -4705,9 +4695,9 @@
         // All activities that came from the package must be
         // restarted as if there was a config change.
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord a = activities.get(activityNdx);
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord a = task.getChildAt(activityNdx);
                 if (a.info.packageName.equals(packageName)) {
                     a.forceNewConfig = true;
                     if (starting != null && a == starting && a.visible) {
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index f1284d81..ca74196 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -44,7 +44,7 @@
 import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Display.TYPE_VIRTUAL;
@@ -690,7 +690,7 @@
     ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags,
             int filterCallingUid) {
         try {
-            Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent");
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resolveIntent");
             int modifiedFlags = flags
                     | PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS;
             if (intent.isWebIntent()
@@ -711,7 +711,7 @@
                 Binder.restoreCallingIdentity(token);
             }
         } finally {
-            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
 
@@ -824,7 +824,7 @@
                         System.identityHashCode(r), task.mTaskId, r.shortComponentName);
                 if (r.isActivityTypeHome()) {
                     // Home process is the root process of the task.
-                    updateHomeProcess(task.mActivities.get(0).app);
+                    updateHomeProcess(task.getChildAt(0).app);
                 }
                 mService.getPackageManagerInternalLocked().notifyPackageUse(
                         r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
@@ -1629,7 +1629,7 @@
             mPendingTempOtherTaskInsetBounds = copyOrNull(tempOtherTaskInsetBounds);
         }
 
-        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeDockedStack");
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resizeDockedStack");
         mService.deferWindowLayout();
         try {
             // Don't allow re-entry while resizing. E.g. due to docked stack detaching.
@@ -1695,7 +1695,7 @@
         } finally {
             mAllowDockedStackResize = true;
             mService.continueWindowLayout();
-            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
 
@@ -1717,7 +1717,7 @@
             return;
         }
 
-        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizePinnedStack");
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resizePinnedStack");
         mService.deferWindowLayout();
         try {
             Rect insetBounds = null;
@@ -1739,7 +1739,7 @@
                     !DEFER_RESUME);
         } finally {
             mService.continueWindowLayout();
-            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
 
@@ -1796,6 +1796,7 @@
             tr.removeTaskActivitiesLocked(reason);
             cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
             mService.getLockTaskController().clearLockedTask(tr);
+            mService.getTaskChangeNotificationController().notifyTaskStackChanged();
             if (tr.isPersistable) {
                 mService.notifyTaskPersisterLocked(null, true);
             }
@@ -1899,9 +1900,9 @@
         task.createTask(onTop, true /* showForAllUsers */);
         if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
                 "Added restored task=" + task + " to stack=" + stack);
-        final ArrayList<ActivityRecord> activities = task.mActivities;
-        for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-            activities.get(activityNdx).setTask(task);
+        for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+            final ActivityRecord r = task.getChildAt(activityNdx);
+            r.setTask(task);
         }
         return true;
     }
@@ -2501,8 +2502,8 @@
             return;
         }
 
-        for (int i = task.mActivities.size() - 1; i >= 0; i--) {
-            final ActivityRecord r = task.mActivities.get(i);
+        for (int i = task.getChildCount() - 1; i >= 0; i--) {
+            final ActivityRecord r = task.getChildAt(i);
             if (r.attachedToProcess()) {
                 mMultiWindowModeChangedActivities.add(r);
             }
@@ -2524,8 +2525,8 @@
     }
 
     void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds) {
-        for (int i = task.mActivities.size() - 1; i >= 0; i--) {
-            final ActivityRecord r = task.mActivities.get(i);
+        for (int i = task.getChildCount() - 1; i >= 0; i--) {
+            final ActivityRecord r = task.getChildAt(i);
             if (r.attachedToProcess()) {
                 mPipModeChangedActivities.add(r);
                 // If we are scheduling pip change, then remove this activity from multi-window
@@ -2543,8 +2544,8 @@
 
     void updatePictureInPictureMode(TaskRecord task, Rect targetStackBounds, boolean forceUpdate) {
         mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG);
-        for (int i = task.mActivities.size() - 1; i >= 0; i--) {
-            final ActivityRecord r = task.mActivities.get(i);
+        for (int i = task.getChildCount() - 1; i >= 0; i--) {
+            final ActivityRecord r = task.getChildAt(i);
             if (r.attachedToProcess()) {
                 r.updatePictureInPictureMode(targetStackBounds, forceUpdate);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 9397893..1a80006 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -770,13 +770,13 @@
         boolean restrictedBgActivity = false;
         if (!abort) {
             try {
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
                         "shouldAbortBackgroundActivityStart");
                 restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid,
                         callingPid, callingPackage, realCallingUid, realCallingPid, callerApp,
                         originatingPendingIntent, allowBackgroundActivityStart, intent);
             } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
             }
         }
 
@@ -1401,41 +1401,12 @@
         final ActivityStack startedActivityStack;
         try {
             mService.deferWindowLayout();
-            result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
+            result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
                     startFlags, doResume, options, inTask, outActivity, restrictedBgActivity);
         } finally {
-            final ActivityStack currentStack = r.getActivityStack();
-            startedActivityStack = currentStack != null ? currentStack : mTargetStack;
-
-            if (ActivityManager.isStartResultSuccessful(result)) {
-                if (startedActivityStack != null) {
-                    // If there is no state change (e.g. a resumed activity is reparented to
-                    // top of another display) to trigger a visibility/configuration checking,
-                    // we have to update the configuration for changing to different display.
-                    final ActivityRecord currentTop =
-                            startedActivityStack.topRunningActivityLocked();
-                    if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
-                        mRootActivityContainer.ensureVisibilityAndConfig(
-                                currentTop, currentTop.getDisplayId(),
-                                true /* markFrozenIfConfigChanged */, false /* deferResume */);
-                    }
-                }
-            } else {
-                // If we are not able to proceed, disassociate the activity from the task.
-                // Leaving an activity in an incomplete state can lead to issues, such as
-                // performing operations without a window container.
-                final ActivityStack stack = mStartActivity.getActivityStack();
-                if (stack != null) {
-                    mStartActivity.finishIfPossible("startActivity", true /* oomAdj */);
-                }
-
-                // Stack should also be detached from display and be removed if it's empty.
-                if (startedActivityStack != null && startedActivityStack.isAttached()
-                        && startedActivityStack.numActivities() == 0
-                        && !startedActivityStack.isActivityTypeHome()) {
-                    startedActivityStack.remove();
-                }
-            }
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+            startedActivityStack = handleStartResult(r, result);
             mService.continueWindowLayout();
         }
 
@@ -1445,6 +1416,49 @@
     }
 
     /**
+     * If the start result is success, ensure that the configuration of the started activity matches
+     * the current display. Otherwise clean up unassociated containers to avoid leakage.
+     *
+     * @return the stack where the successful started activity resides.
+     */
+    private @Nullable ActivityStack handleStartResult(@NonNull ActivityRecord started, int result) {
+        final ActivityStack currentStack = started.getActivityStack();
+        ActivityStack startedActivityStack = currentStack != null ? currentStack : mTargetStack;
+
+        if (ActivityManager.isStartResultSuccessful(result)) {
+            if (startedActivityStack != null) {
+                // If there is no state change (e.g. a resumed activity is reparented to top of
+                // another display) to trigger a visibility/configuration checking, we have to
+                // update the configuration for changing to different display.
+                final ActivityRecord currentTop = startedActivityStack.topRunningActivityLocked();
+                if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
+                    mRootActivityContainer.ensureVisibilityAndConfig(
+                            currentTop, currentTop.getDisplayId(),
+                            true /* markFrozenIfConfigChanged */, false /* deferResume */);
+                }
+            }
+            return startedActivityStack;
+        }
+
+        // If we are not able to proceed, disassociate the activity from the task. Leaving an
+        // activity in an incomplete state can lead to issues, such as performing operations
+        // without a window container.
+        final ActivityStack stack = mStartActivity.getActivityStack();
+        if (stack != null) {
+            mStartActivity.finishIfPossible("startActivity", true /* oomAdj */);
+        }
+
+        // Stack should also be detached from display and be removed if it's empty.
+        if (startedActivityStack != null && startedActivityStack.isAttached()
+                && startedActivityStack.numActivities() == 0
+                && !startedActivityStack.isActivityTypeHome()) {
+            startedActivityStack.remove();
+            startedActivityStack = null;
+        }
+        return startedActivityStack;
+    }
+
+    /**
      * Return true if background activity is really aborted.
      *
      * TODO(b/131748165): Refactor the logic so we don't need to call this method everywhere.
@@ -1469,7 +1483,7 @@
     }
 
     // Note: This method should only be called from {@link startActivity}.
-    private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
+    private int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
             IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
             int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
             ActivityRecord[] outActivity, boolean restrictedBgActivity) {
@@ -1592,7 +1606,7 @@
                 // accordingly.
                 if (mTargetStack.isFocusable()
                         && !mRootActivityContainer.isTopDisplayFocusedStack(mTargetStack)) {
-                    mTargetStack.moveToFront("startActivityUnchecked");
+                    mTargetStack.moveToFront("startActivityInner");
                 }
                 mRootActivityContainer.resumeFocusedStacksTopActivities(
                         mTargetStack, mStartActivity, mOptions);
@@ -1873,7 +1887,7 @@
                     mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
                             mLaunchFlags, mOptions);
                     mTargetStack.addTask(targetTask,
-                            !mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
+                            !mLaunchTaskBehind /* toTop */, "complyActivityFlags");
                 }
             }
         } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) == 0 && !mAddingToTask
@@ -2430,7 +2444,7 @@
         if (mStartActivity.getTaskRecord() == null || mStartActivity.getTaskRecord() == parent) {
             parent.addActivityToTop(mStartActivity);
         } else {
-            mStartActivity.reparent(parent, parent.mActivities.size() /* top */, reason);
+            mStartActivity.reparent(parent, parent.getChildCount() /* top */, reason);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index ab35652..0488a3b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -159,10 +159,10 @@
      *
      * @param reasons A map from windowing mode to a reason integer why the transition was started,
      *                which must be one of the APP_TRANSITION_* values.
-     * @param timestamp The time at which the app transition started in
-     *                  {@link SystemClock#uptimeMillis()} timebase.
+     * @param timestampNs The time at which the app transition started in
+     *                  {@link SystemClock#elapsedRealtimeNs()} ()} timebase.
      */
-    public abstract void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp);
+    public abstract void notifyAppTransitionStarting(SparseIntArray reasons, long timestampNs);
 
     /**
      * Callback for window manager to let activity manager know that the app transition was
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 750fc68..20113a6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -52,7 +52,7 @@
 import static android.os.FactoryTest.FACTORY_TEST_OFF;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.SYSTEM_UID;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
@@ -63,7 +63,6 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
 
 import static com.android.server.am.ActivityManagerService.ANR_TRACE_DIR;
 import static com.android.server.am.ActivityManagerService.MY_PID;
@@ -1607,6 +1606,7 @@
             }
 
             final long origId = Binder.clearCallingIdentity();
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishActivity");
             try {
                 boolean res;
                 final boolean finishWithRootActivity =
@@ -1634,6 +1634,7 @@
                 }
                 return res;
             } finally {
+                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                 Binder.restoreCallingIdentity(origId);
             }
         }
@@ -1668,6 +1669,7 @@
         try {
             WindowProcessController proc = null;
             synchronized (mGlobalLock) {
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityIdle");
                 ActivityStack stack = ActivityRecord.getStackLocked(token);
                 if (stack == null) {
                     return;
@@ -1682,6 +1684,7 @@
                 }
             }
         } finally {
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             Binder.restoreCallingIdentity(origId);
         }
     }
@@ -1708,10 +1711,12 @@
     public final void activityPaused(IBinder token) {
         final long origId = Binder.clearCallingIdentity();
         synchronized (mGlobalLock) {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityPaused");
             ActivityStack stack = ActivityRecord.getStackLocked(token);
             if (stack != null) {
                 stack.activityPausedLocked(token, false);
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
         Binder.restoreCallingIdentity(origId);
     }
@@ -1732,6 +1737,7 @@
         int restartingUid = 0;
         final ActivityRecord r;
         synchronized (mGlobalLock) {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityStopped");
             r = ActivityRecord.isInStackLocked(token);
             if (r != null) {
                 if (r.attachedToProcess()
@@ -1743,6 +1749,7 @@
                 }
                 r.activityStoppedLocked(icicle, persistentState, description);
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         if (restartingName != null) {
@@ -1764,12 +1771,14 @@
         if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "ACTIVITY DESTROYED: " + token);
         synchronized (mGlobalLock) {
             final long origId = Binder.clearCallingIdentity();
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityDestroyed");
             try {
                 final ActivityRecord activity = ActivityRecord.forTokenLocked(token);
                 if (activity != null) {
                     activity.destroyed("activityDestroyed");
                 }
             } finally {
+                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                 Binder.restoreCallingIdentity(origId);
             }
         }
@@ -1982,7 +1991,7 @@
                 final TaskRecord task = r.getTaskRecord();
                 int index = task.mActivities.lastIndexOf(r);
                 if (index > 0) {
-                    ActivityRecord under = task.mActivities.get(index - 1);
+                    ActivityRecord under = task.getChildAt(index - 1);
                     under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null;
                 }
                 return r.setOccludesParent(false);
@@ -3321,29 +3330,6 @@
     }
 
     @Override
-    public void startInPlaceAnimationOnFrontMostApplication(Bundle opts) {
-        final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(opts);
-        final ActivityOptions activityOptions = safeOptions != null
-                ? safeOptions.getOptions(mStackSupervisor)
-                : null;
-        if (activityOptions == null
-                || activityOptions.getAnimationType() != ActivityOptions.ANIM_CUSTOM_IN_PLACE
-                || activityOptions.getCustomInPlaceResId() == 0) {
-            throw new IllegalArgumentException("Expected in-place ActivityOption " +
-                    "with valid animation");
-        }
-        // Get top display of front most application.
-        final ActivityStack focusedStack = getTopDisplayFocusedStack();
-        if (focusedStack != null) {
-            final DisplayContent dc = focusedStack.getDisplay().mDisplayContent;
-            dc.prepareAppTransition(TRANSIT_TASK_IN_PLACE, false);
-            dc.mAppTransition.overrideInPlaceAppTransition(activityOptions.getPackageName(),
-                    activityOptions.getCustomInPlaceResId());
-            dc.executeAppTransition();
-        }
-    }
-
-    @Override
     public void removeStack(int stackId) {
         enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "removeStack()");
         synchronized (mGlobalLock) {
@@ -5530,8 +5516,8 @@
     void startProcessAsync(ActivityRecord activity, boolean knownToBeDead, boolean isTop,
             String hostingType) {
         try {
-            if (Trace.isTagEnabled(TRACE_TAG_ACTIVITY_MANAGER)) {
-                Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dispatchingStartProcess:"
+            if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "dispatchingStartProcess:"
                         + activity.processName);
             }
             // Post message to start process to avoid possible deadlock of calling into AMS with the
@@ -5541,7 +5527,7 @@
                     isTop, hostingType, activity.intent.getComponent());
             mH.sendMessage(m);
         } finally {
-            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
 
@@ -5694,11 +5680,11 @@
 
     private void updateResumedAppTrace(@Nullable ActivityRecord resumed) {
         if (mTracedResumedActivity != null) {
-            Trace.asyncTraceEnd(TRACE_TAG_ACTIVITY_MANAGER,
+            Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER,
                     constructResumedTraceName(mTracedResumedActivity.packageName), 0);
         }
         if (resumed != null) {
-            Trace.asyncTraceBegin(TRACE_TAG_ACTIVITY_MANAGER,
+            Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER,
                     constructResumedTraceName(resumed.packageName), 0);
         }
         mTracedResumedActivity = resumed;
@@ -6026,10 +6012,10 @@
 
         @Override
         public void notifyAppTransitionStarting(SparseIntArray reasons,
-                long timestamp) {
+                long timestampNs) {
             synchronized (mGlobalLock) {
                 mStackSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
-                        reasons, timestamp);
+                        reasons, timestampNs);
             }
         }
 
@@ -6797,7 +6783,14 @@
         @Override
         public boolean attachApplication(WindowProcessController wpc) throws RemoteException {
             synchronized (mGlobalLockWithoutBoost) {
-                return mRootActivityContainer.attachApplication(wpc);
+                if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "attachApplication:" + wpc.mName);
+                }
+                try {
+                    return mRootActivityContainer.attachApplication(wpc);
+                } finally {
+                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 93c461f..394b475 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -33,7 +33,6 @@
 import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
 import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
 import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
 import static android.view.WindowManager.TRANSIT_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
@@ -2257,8 +2256,7 @@
     static boolean isTaskTransit(int transit) {
         return isTaskOpenTransit(transit)
                 || transit == TRANSIT_TASK_CLOSE
-                || transit == TRANSIT_TASK_TO_BACK
-                || transit == TRANSIT_TASK_IN_PLACE;
+                || transit == TRANSIT_TASK_TO_BACK;
     }
 
     private static  boolean isTaskOpenTransit(int transit) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 20a871b..0c07e15 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -31,7 +31,6 @@
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
 import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
 import static android.view.WindowManager.TRANSIT_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
@@ -179,8 +178,6 @@
         final int layoutRedo;
         mService.mSurfaceAnimationRunner.deferStartingAnimations();
         try {
-            processApplicationsAnimatingInPlace(transit);
-
             handleClosingApps(transit, animLp, voiceInteraction);
             handleOpeningApps(transit, animLp, voiceInteraction);
             handleChangingApps(transit, animLp, voiceInteraction);
@@ -212,7 +209,7 @@
         mDisplayContent.computeImeTarget(true /* updateImeTarget */);
 
         mService.mAtmInternal.notifyAppTransitionStarting(mTempTransitionReasons.clone(),
-                SystemClock.uptimeMillis());
+            SystemClock.elapsedRealtimeNanos());
 
         if (transit == TRANSIT_SHOW_SINGLE_TASK_DISPLAY) {
             mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
@@ -710,19 +707,4 @@
         }
         return topApp;
     }
-
-    private void processApplicationsAnimatingInPlace(int transit) {
-        if (transit == TRANSIT_TASK_IN_PLACE) {
-            // Find the focused window
-            final WindowState win = mDisplayContent.findFocusedWindow();
-            if (win != null) {
-                final AppWindowToken wtoken = win.mAppToken;
-                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now animating app in place %s", wtoken);
-                wtoken.cancelAnimation();
-                wtoken.applyAnimationLocked(null, transit, false, false);
-                wtoken.updateReportedVisibilityLocked();
-                wtoken.showAllWindowsLocked();
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index a261341..e56fdd2 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -512,7 +512,7 @@
         if (DEBUG_VISIBILITY) Slog.v(TAG, "VIS " + this + ": interesting="
                 + numInteresting + " visible=" + numVisible);
         if (nowDrawn != reportedDrawn) {
-            onWindowsDrawn(nowDrawn, SystemClock.uptimeMillis());
+            onWindowsDrawn(nowDrawn, SystemClock.elapsedRealtimeNanos());
             reportedDrawn = nowDrawn;
         }
         if (nowVisible != reportedVisible) {
@@ -1420,7 +1420,7 @@
         mReparenting = true;
 
         getParent().removeChild(this);
-        task.addChild(this, position);
+        task.addChild((ActivityRecord) this, position);
 
         mReparenting = false;
 
@@ -2283,7 +2283,7 @@
         }
     }
 
-    private final Runnable mAddStartingWindow = new Runnable() {
+    private class AddStartingWindow implements Runnable {
 
         @Override
         public void run() {
@@ -2343,7 +2343,9 @@
                         AppWindowToken.this);
             }
         }
-    };
+    }
+
+    private final AddStartingWindow mAddStartingWindow = new AddStartingWindow();
 
     private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning,
             boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents,
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 8afbbdf..300ee1d 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -626,6 +626,10 @@
         return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
     }
 
+    boolean hasChild() {
+        return getChildCount() > 0;
+    }
+
     abstract protected int getChildCount();
 
     abstract protected E getChildAt(int index);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ac910cd..4c3611e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5152,7 +5152,7 @@
         // Let surface flinger to set the display ID of this input window handle because we don't
         // know which display the parent surface control is on.
         final InputWindowHandle portalWindowHandle = new InputWindowHandle(
-                null /* inputApplicationHandle */, null /* clientWindow */, INVALID_DISPLAY);
+                null /* inputApplicationHandle */, INVALID_DISPLAY);
         portalWindowHandle.name = name;
         portalWindowHandle.token = new Binder();
         portalWindowHandle.layoutParamsFlags =
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 7be4dbd..60e9819 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -16,13 +16,12 @@
 
 package com.android.server.wm;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
 import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
 import static android.view.Display.TYPE_BUILT_IN;
@@ -197,6 +196,11 @@
     // Nav bar is never forced opaque.
     private static final int NAV_BAR_FORCE_TRANSPARENT = 2;
 
+    /** Don't apply window animation (see {@link #selectAnimation}). */
+    static final int ANIMATION_NONE = -1;
+    /** Use the transit animation in style resource (see {@link #selectAnimation}). */
+    static final int ANIMATION_STYLEABLE = 0;
+
     /**
      * These are the system UI flags that, when changing, can cause the layout
      * of the screen to change.
@@ -1037,9 +1041,9 @@
     }
 
     /**
-     * Control the animation to run when a window's state changes.  Return a
-     * non-0 number to force the animation to a specific resource ID, or 0
-     * to use the default animation.
+     * Control the animation to run when a window's state changes.  Return a positive number to
+     * force the animation to a specific resource ID, {@link #ANIMATION_STYLEABLE} to use the
+     * style resource defining the animation, or {@link #ANIMATION_NONE} for no animation.
      *
      * @param win The window that is changing.
      * @param transit What is happening to the window:
@@ -1048,9 +1052,9 @@
      *                {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_SHOW}, or
      *                {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_HIDE}.
      *
-     * @return Resource ID of the actual animation to use, or 0 for none.
+     * @return Resource ID of the actual animation to use, or {@link #ANIMATION_NONE} for none.
      */
-    public int selectAnimationLw(WindowState win, int transit) {
+    int selectAnimation(WindowState win, int transit) {
         if (DEBUG_ANIM) Slog.i(TAG, "selectAnimation in " + win
                 + ": transit=" + transit);
         if (win == mStatusBar) {
@@ -1058,7 +1062,7 @@
             final boolean expanded = win.getAttrs().height == MATCH_PARENT
                     && win.getAttrs().width == MATCH_PARENT;
             if (isKeyguard || expanded) {
-                return -1;
+                return ANIMATION_NONE;
             }
             if (transit == TRANSIT_EXIT
                     || transit == TRANSIT_HIDE) {
@@ -1069,7 +1073,7 @@
             }
         } else if (win == mNavigationBar) {
             if (win.getAttrs().windowAnimations != 0) {
-                return 0;
+                return ANIMATION_STYLEABLE;
             }
             // This can be on either the bottom or the right or the left.
             if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
@@ -1102,7 +1106,7 @@
                 }
             }
         } else if (win.getAttrs().type == TYPE_DOCK_DIVIDER) {
-            return selectDockedDividerAnimationLw(win, transit);
+            return selectDockedDividerAnimation(win, transit);
         }
 
         if (transit == TRANSIT_PREVIEW_DONE) {
@@ -1116,13 +1120,13 @@
             // is shown.  We don't want an animation on the dream, because
             // we need it shown immediately with the keyguard animating away
             // to reveal it.
-            return -1;
+            return ANIMATION_NONE;
         }
 
-        return 0;
+        return ANIMATION_STYLEABLE;
     }
 
-    private int selectDockedDividerAnimationLw(WindowState win, int transit) {
+    private int selectDockedDividerAnimation(WindowState win, int transit) {
         int insets = mDisplayContent.getDockedDividerController().getContentInsets();
 
         // If the divider is behind the navigation bar, don't animate.
@@ -1141,14 +1145,14 @@
                 || frame.bottom + insets >= win.getDisplayFrameLw().bottom);
         final boolean offscreen = offscreenLandscape || offscreenPortrait;
         if (behindNavBar || offscreen) {
-            return 0;
+            return ANIMATION_STYLEABLE;
         }
         if (transit == TRANSIT_ENTER || transit == TRANSIT_SHOW) {
             return R.anim.fade_in;
         } else if (transit == TRANSIT_EXIT) {
             return R.anim.fade_out;
         } else {
-            return 0;
+            return ANIMATION_STYLEABLE;
         }
     }
 
@@ -3130,9 +3134,10 @@
         final int dockedVisibility = updateLightStatusBarLw(0 /* vis */,
                 mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
         mService.getStackBounds(
-                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mNonDockedStackBounds);
-        mService.getStackBounds(
                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDockedStackBounds);
+        mService.getStackBounds(mDockedStackBounds.isEmpty()
+                        ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+                ACTIVITY_TYPE_UNDEFINED, mNonDockedStackBounds);
         final Pair<Integer, Boolean> result =
                 updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
         final int visibility = result.first;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 34820ac..abd7222 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -263,7 +263,7 @@
             InputChannel[] channels = InputChannel.openInputChannelPair("drag");
             mServerChannel = channels[0];
             mClientChannel = channels[1];
-            mService.mInputManager.registerInputChannel(mServerChannel, null);
+            mService.mInputManager.registerInputChannel(mServerChannel);
             mInputEventReceiver = new DragInputEventReceiver(mClientChannel,
                     mService.mH.getLooper(), mDragDropController);
 
@@ -272,7 +272,7 @@
             mDragApplicationHandle.dispatchingTimeoutNanos =
                     WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
 
-            mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
+            mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle,
                     display.getDisplayId());
             mDragWindowHandle.name = "drag";
             mDragWindowHandle.token = mServerChannel.getToken();
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 7e085f6..bc95481 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -25,7 +25,7 @@
  */
 class ImeInsetsSourceProvider extends InsetsSourceProvider {
 
-    private WindowState mCurImeTarget;
+    private WindowState mImeTargetFromIme;
     private Runnable mShowImeRunner;
     private boolean mIsImeLayoutDrawn;
 
@@ -40,8 +40,8 @@
     void onPostLayout() {
         super.onPostLayout();
 
-        if (mCurImeTarget != null
-                && mCurImeTarget == mDisplayContent.mInputMethodTarget
+        if (mImeTargetFromIme != null
+                && isImeTargetFromDisplayContentAndImeSame()
                 && mWin != null
                 && mWin.isDrawnLw()
                 && !mWin.mGivenInsetsPending) {
@@ -64,18 +64,33 @@
     /**
      * Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService}
      * requests to show IME on {@param imeTarget}.
-     * @param imeTarget imeTarget on which IME is displayed.
+     * @param imeTarget imeTarget on which IME request is coming from.
      */
     void scheduleShowImePostLayout(WindowState imeTarget) {
-        mCurImeTarget = imeTarget;
+        mImeTargetFromIme = imeTarget;
         mShowImeRunner = () -> {
             // Target should still be the same.
-            if (mCurImeTarget == mDisplayContent.mInputMethodTarget) {
+            if (isImeTargetFromDisplayContentAndImeSame()) {
                 mDisplayContent.mInputMethodTarget.showInsets(
                         WindowInsets.Type.ime(), true /* fromIme */);
             }
-            mCurImeTarget = null;
+            mImeTargetFromIme = null;
         };
     }
 
+    private boolean isImeTargetFromDisplayContentAndImeSame() {
+        // IMMS#mLastImeTargetWindow always considers focused window as
+        // IME target, however DisplayContent#computeImeTarget() can compute
+        // a different IME target.
+        // Refer to WindowManagerService#applyImeVisibility(token, false).
+        // If IMMS's imeTarget is child of DisplayContent's imeTarget and child window
+        // is above the parent, we will consider it as the same target for now.
+        // TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of
+        //  actual IME target.
+        return mImeTargetFromIme == mDisplayContent.mInputMethodTarget
+                || (mDisplayContent.mInputMethodTarget.getParentWindow() == mImeTargetFromIme
+                        && mDisplayContent.mInputMethodTarget.mSubLayer
+                                > mImeTargetFromIme.mSubLayer);
+    }
+
 }
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index d774dc3..82ac6b8 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -189,7 +189,7 @@
                         | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                 PixelFormat.TRANSLUCENT);
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("ImmersiveModeConfirmation");
         lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation;
         lp.token = getWindowToken();
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 8dae016..ebe9f08 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -65,14 +65,14 @@
         } else {
             mClientChannel = channels[1];
         }
-        mService.mInputManager.registerInputChannel(mServerChannel, null);
+        mService.mInputManager.registerInputChannel(mServerChannel);
 
         mApplicationHandle = new InputApplicationHandle(new Binder());
         mApplicationHandle.name = name;
         mApplicationHandle.dispatchingTimeoutNanos =
                 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
 
-        mWindowHandle = new InputWindowHandle(mApplicationHandle, null, displayId);
+        mWindowHandle = new InputWindowHandle(mApplicationHandle, displayId);
         mWindowHandle.name = name;
         mWindowHandle.token = mServerChannel.getToken();
         mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index ec36a82..9973e11 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -5,20 +5,25 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
 
 import android.os.Debug;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.Slog;
+import android.view.IWindow;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
 import com.android.server.input.InputManagerService;
 
 import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicReference;
 
 final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "InputManagerCallback" : TAG_WM;
     private final WindowManagerService mService;
 
     // Set to true when the first input device configuration change notification
@@ -37,6 +42,13 @@
     // which point the ActivityManager will enable dispatching.
     private boolean mInputDispatchEnabled;
 
+    // TODO(b/141749603)) investigate if this can be part of client focus change dispatch
+    // Tracks the currently focused window used to update pointer capture state in clients
+    private AtomicReference<IWindow> mFocusedWindow = new AtomicReference<>();
+
+    // Tracks focused window pointer capture state
+    private boolean mFocusedWindowHasCapture;
+
     public InputManagerCallback(WindowManagerService service) {
         mService = service;
     }
@@ -53,7 +65,7 @@
         }
 
         synchronized (mService.mGlobalLock) {
-            WindowState windowState = mService.windowForClientLocked(null, token, false);
+            WindowState windowState = mService.mInputToWindowMap.get(token);
             if (windowState != null) {
                 Slog.i(TAG_WM, "WINDOW DIED " + windowState);
                 windowState.removeIfPossible();
@@ -72,9 +84,10 @@
         AppWindowToken appWindowToken = null;
         WindowState windowState = null;
         boolean aboveSystem = false;
+        //TODO(b/141764879) Limit scope of wm lock when input calls notifyANR
         synchronized (mService.mGlobalLock) {
             if (token != null) {
-                windowState = mService.windowForClientLocked(null, token, false);
+                windowState = mService.mInputToWindowMap.get(token);
                 if (windowState != null) {
                     appWindowToken = windowState.mAppToken;
                 }
@@ -109,7 +122,7 @@
             // Notify the activity manager about the timeout and let it decide whether
             // to abort dispatching or keep waiting.
             final boolean abort = appWindowToken.keyDispatchingTimedOut(reason,
-                    (windowState != null) ? windowState.mSession.mPid : -1);
+                    windowState.mSession.mPid);
             if (!abort) {
                 // The activity manager declined to abort dispatching.
                 // Wait a bit longer and timeout again later.
@@ -239,6 +252,60 @@
         mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget();
     }
 
+    @Override
+    public boolean notifyFocusChanged(IBinder oldToken, IBinder newToken) {
+        boolean requestRefreshConfiguration = false;
+        final IWindow newFocusedWindow;
+        final WindowState win;
+
+        // TODO(b/141749603) investigate if this can be part of client focus change dispatch
+        synchronized (mService.mGlobalLock) {
+            win = mService.mInputToWindowMap.get(newToken);
+        }
+        newFocusedWindow = (win != null) ? win.mClient : null;
+
+        final IWindow focusedWindow = mFocusedWindow.get();
+        if (focusedWindow != null) {
+            if (newFocusedWindow != null
+                    && newFocusedWindow.asBinder() == focusedWindow.asBinder()) {
+                Slog.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow="
+                        + focusedWindow);
+                return false;
+            }
+            requestRefreshConfiguration = dispatchPointerCaptureChanged(focusedWindow, false);
+        }
+        mFocusedWindow.set(newFocusedWindow);
+        return requestRefreshConfiguration;
+    }
+
+    @Override
+    public boolean requestPointerCapture(IBinder windowToken, boolean enabled) {
+        final IWindow focusedWindow = mFocusedWindow.get();
+        if (focusedWindow == null || focusedWindow.asBinder() != windowToken) {
+            Slog.e(TAG, "requestPointerCapture called for a window that has no focus: "
+                    + windowToken);
+            return false;
+        }
+        if (mFocusedWindowHasCapture == enabled) {
+            Slog.i(TAG, "requestPointerCapture: already " + (enabled ? "enabled" : "disabled"));
+            return false;
+        }
+        return dispatchPointerCaptureChanged(focusedWindow, enabled);
+    }
+
+    private boolean dispatchPointerCaptureChanged(IWindow focusedWindow, boolean enabled) {
+        if (mFocusedWindowHasCapture != enabled) {
+            mFocusedWindowHasCapture = enabled;
+            try {
+                focusedWindow.dispatchPointerCaptureChanged(enabled);
+            } catch (RemoteException ex) {
+                /* ignore */
+            }
+            return true;
+        }
+        return false;
+    }
+
     /** Waits until the built-in input devices have been configured. */
     public boolean waitForInputDevicesReady(long timeoutMillis) {
         synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 932b4fa..584c6e1 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -112,7 +112,7 @@
         }
     }
 
-    private final Runnable mUpdateInputWindows = new Runnable() {
+    private class UpdateInputWindows implements Runnable {
         @Override
         public void run() {
             synchronized (mService.mGlobalLock) {
@@ -148,7 +148,9 @@
                 mUpdateInputForAllWindowsConsumer.updateInputWindows(inDrag);
             }
         }
-    };
+    }
+
+    private final UpdateInputWindows mUpdateInputWindows = new UpdateInputWindows();
 
     public InputMonitor(WindowManagerService service, int displayId) {
         mService = service;
@@ -411,7 +413,7 @@
         WallpaperController wallpaperController;
 
         // An invalid window handle that tells SurfaceFlinger not update the input info.
-        final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, null, mDisplayId);
+        final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, mDisplayId);
 
         private void updateInputWindows(boolean inDrag) {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index b7184a5..22ba82a 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -33,4 +33,13 @@
      */
     default void showInsets(@InsetType int types, boolean fromIme) {
     }
+
+    /**
+     * Instructs the control target to hide inset sources.
+     *
+     * @param types to specify which types of insets source window should be hidden.
+     * @param fromIme {@code true} if IME hide request originated from {@link InputMethodService}.
+     */
+    default void hideInsets(@InsetType int types, boolean fromIme) {
+    }
 }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index cc55e01..3731d3f 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -241,6 +241,10 @@
         @Override
         public void startAnimation(SurfaceControl animationLeash, Transaction t,
                 OnAnimationFinishedCallback finishCallback) {
+            // TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed.
+            t.setAlpha(animationLeash, 1 /* alpha */);
+            t.hide(animationLeash);
+
             mCapturedLeash = animationLeash;
             final Rect frame = mWin.getWindowFrames().mFrame;
             t.setPosition(mCapturedLeash, frame.left, frame.top);
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 2b5eb3a..52cc422 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
@@ -180,7 +180,7 @@
         if (!mKeyguardShowing) {
             return;
         }
-        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway");
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "keyguardGoingAway");
         mService.deferWindowLayout();
         try {
             setKeyguardGoingAway(true);
@@ -202,11 +202,8 @@
                     true /* taskSwitch */);
             mWindowManager.executeAppTransition();
         } finally {
-            Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway: surfaceLayout");
             mService.continueWindowLayout();
-            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
-
-            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
index 93e2d8d..362ed3c 100644
--- a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
+++ b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
@@ -70,9 +70,12 @@
     }
 
     @Override
-    public void onIntentStarted(Intent intent) {
+    public void onIntentStarted(Intent intent, long timestampNs) {
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                LaunchObserverRegistryImpl::handleOnIntentStarted, this, intent));
+                LaunchObserverRegistryImpl::handleOnIntentStarted,
+                this,
+                intent,
+                timestampNs));
     }
 
     @Override
@@ -99,9 +102,22 @@
 
     @Override
     public void onActivityLaunchFinished(
-        @ActivityRecordProto byte[] activity) {
+        @ActivityRecordProto byte[] activity,
+        long timestampNs) {
         mHandler.sendMessage(PooledLambda.obtainMessage(
-                LaunchObserverRegistryImpl::handleOnActivityLaunchFinished, this, activity));
+            LaunchObserverRegistryImpl::handleOnActivityLaunchFinished,
+            this,
+            activity,
+            timestampNs));
+    }
+
+    @Override
+    public void onReportFullyDrawn(@ActivityRecordProto byte[] activity, long timestampNs) {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+            LaunchObserverRegistryImpl::handleOnReportFullyDrawn,
+            this,
+            activity,
+            timestampNs));
     }
 
     // Use PooledLambda.obtainMessage to invoke below methods. Every method reference must be
@@ -116,11 +132,11 @@
         mList.remove(observer);
     }
 
-    private void handleOnIntentStarted(Intent intent) {
+    private void handleOnIntentStarted(Intent intent, long timestampNs) {
         // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
         for (int i = 0; i < mList.size(); i++) {
              ActivityMetricsLaunchObserver o = mList.get(i);
-             o.onIntentStarted(intent);
+             o.onIntentStarted(intent, timestampNs);
         }
     }
 
@@ -152,11 +168,20 @@
     }
 
     private void handleOnActivityLaunchFinished(
-            @ActivityRecordProto byte[] activity) {
+            @ActivityRecordProto byte[] activity, long timestampNs) {
         // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
         for (int i = 0; i < mList.size(); i++) {
             ActivityMetricsLaunchObserver o = mList.get(i);
-            o.onActivityLaunchFinished(activity);
+            o.onActivityLaunchFinished(activity, timestampNs);
+        }
+    }
+
+    private void handleOnReportFullyDrawn(
+            @ActivityRecordProto byte[] activity, long timestampNs) {
+        // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
+        for (int i = 0; i < mList.size(); i++) {
+            ActivityMetricsLaunchObserver o = mList.get(i);
+            o.onReportFullyDrawn(activity, timestampNs);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 94d010e..c59a73b 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -20,7 +20,7 @@
 
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.os.Binder;
+import android.os.IBinder;
 import android.os.Process;
 import android.view.InputChannel;
 import android.view.InputEventReceiver;
@@ -170,7 +170,7 @@
         final InputWindowHandle mWindowHandle;
         final InputEventReceiver mInputEventReceiver;
         final WindowManagerService mWmService;
-        final Binder mToken = new Binder();
+        final IBinder mToken;
 
         InputInterceptor(String namePrefix, WindowState win) {
             mWmService = win.mWmService;
@@ -180,10 +180,11 @@
             mClientChannel = channels[1];
             mInputEventReceiver = new SimpleInputReceiver(mClientChannel);
 
-            mWmService.mInputManager.registerInputChannel(mServerChannel, mToken);
+            mWmService.mInputManager.registerInputChannel(mServerChannel);
+            mToken = mServerChannel.getToken();
 
             mWindowHandle = new InputWindowHandle(null /* inputApplicationHandle */,
-                    null /* clientWindow */, win.getDisplayId());
+                    win.getDisplayId());
             mWindowHandle.name = name;
             mWindowHandle.token = mToken;
             mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 062cdc5..12579e6 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -22,7 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.TRANSIT_NONE;
 
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
@@ -158,7 +158,7 @@
 
     void startRecentsActivity(IRecentsAnimationRunner recentsAnimationRunner) {
         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startRecentsActivity(): intent=%s", mTargetIntent);
-        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "RecentsAnimation#startRecentsActivity");
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#startRecentsActivity");
 
         // TODO(multi-display) currently only support recents animation in default display.
         final DisplayContent dc =
@@ -263,7 +263,7 @@
             throw e;
         } finally {
             mService.continueWindowLayout();
-            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
 
@@ -297,7 +297,7 @@
             }
 
             mWindowManager.inSurfaceTransaction(() -> {
-                Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                         "RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
                 mService.deferWindowLayout();
                 try {
@@ -394,7 +394,7 @@
                     throw e;
                 } finally {
                     mService.continueWindowLayout();
-                    Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                 }
             });
         }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index bd27905..d606e5d 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -98,8 +98,10 @@
     private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
             new ArrayList<>();
     private final int mDisplayId;
-    private final Runnable mFailsafeRunnable = () ->
-            cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "failSafeRunnable");
+    private boolean mWillFinishToHome = false;
+    private final Runnable mFailsafeRunnable = () -> cancelAnimation(
+            mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION,
+            "failSafeRunnable");
 
     // The recents component app token that is shown behind the visibile tasks
     private AppWindowToken mTargetAppToken;
@@ -326,6 +328,13 @@
                 }
             }
         }
+
+        @Override
+        public void setWillFinishToHome(boolean willFinishToHome) {
+            synchronized (mService.getWindowManagerLock()) {
+                mWillFinishToHome = willFinishToHome;
+            }
+        }
     };
 
     /**
@@ -494,7 +503,8 @@
         }
         final SparseIntArray reasons = new SparseIntArray();
         reasons.put(WINDOWING_MODE_FULLSCREEN, APP_TRANSITION_RECENTS_ANIM);
-        mService.mAtmInternal.notifyAppTransitionStarting(reasons, SystemClock.uptimeMillis());
+        mService.mAtmInternal
+            .notifyAppTransitionStarting(reasons, SystemClock.elapsedRealtimeNanos());
     }
 
     private RemoteAnimationTarget[] createAppAnimations() {
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index d78d517..60833c3 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -983,7 +983,7 @@
             stack.resize(task.getRequestedOverrideBounds(), null /* tempTaskBounds */,
                     null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS, !DEFER_RESUME);
 
-            if (task.mActivities.size() == 1) {
+            if (task.getChildCount() == 1) {
                 // Defer resume until below, and do not schedule PiP changes until we animate below
                 task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, DEFER_RESUME,
                         false /* schedulePictureInPictureModeChange */, reason);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a181c18..2657826 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -58,7 +58,7 @@
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
-class Task extends WindowContainer<AppWindowToken> implements ConfigurationContainerListener{
+class Task extends WindowContainer<ActivityRecord> implements ConfigurationContainerListener{
     static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
 
     // TODO: Track parent marks like this in WindowContainer.
@@ -120,9 +120,12 @@
 
     // TODO: Remove after unification.
     @Override
-    public void onConfigurationChanged(Configuration newParentConfig) {
-        // Only forward configuration changes in cases where children won't get it from TaskRecord.
-        onConfigurationChanged(newParentConfig, mTaskRecord == null /*forwardToChildren*/);
+    public void onConfigurationChanged(Configuration newParentConfig, boolean forwardToChildren) {
+        // Forward configuration changes in cases
+        // - children won't get it from TaskRecord
+        // - it's a pinned task
+        forwardToChildren &= (mTaskRecord == null) || inPinnedWindowingMode();
+        super.onConfigurationChanged(newParentConfig, forwardToChildren);
     }
 
     Task(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode,
@@ -170,14 +173,14 @@
     }
 
     @Override
-    void addChild(AppWindowToken wtoken, int position) {
+    void addChild(ActivityRecord child, int position) {
         position = getAdjustedAddPosition(position);
-        super.addChild(wtoken, position);
+        super.addChild(child, position);
         mDeferRemoval = false;
     }
 
     @Override
-    void positionChildAt(int position, AppWindowToken child, boolean includingParents) {
+    void positionChildAt(int position, ActivityRecord child, boolean includingParents) {
         position = getAdjustedAddPosition(position);
         super.positionChildAt(position, child, includingParents);
         mDeferRemoval = false;
@@ -279,13 +282,13 @@
     }
 
     @Override
-    void removeChild(AppWindowToken token) {
-        if (!mChildren.contains(token)) {
+    void removeChild(ActivityRecord child) {
+        if (!mChildren.contains(child)) {
             Slog.e(TAG, "removeChild: token=" + this + " not found.");
             return;
         }
 
-        super.removeChild(token);
+        super.removeChild(child);
 
         if (mChildren.isEmpty()) {
             EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeAppToken: last token");
@@ -674,18 +677,18 @@
         return null;
     }
 
-    void positionChildAtTop(AppWindowToken aToken) {
-        positionChildAt(aToken, POSITION_TOP);
+    void positionChildAtTop(ActivityRecord child) {
+        positionChildAt(child, POSITION_TOP);
     }
 
-    void positionChildAt(AppWindowToken aToken, int position) {
-        if (aToken == null) {
+    void positionChildAt(ActivityRecord child, int position) {
+        if (child == null) {
             Slog.w(TAG_WM,
                     "Attempted to position of non-existing app");
             return;
         }
 
-        positionChildAt(position, aToken, false /* includeParents */);
+        positionChildAt(position, child, false /* includeParents */);
     }
 
     void forceWindowsScaleable(boolean force) {
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index b680fa4..478b1b5 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -256,7 +256,7 @@
         final InputChannel[] channels = InputChannel.openInputChannelPair(TAG);
         mServerChannel = channels[0];
         mClientChannel = channels[1];
-        mService.mInputManager.registerInputChannel(mServerChannel, null);
+        mService.mInputManager.registerInputChannel(mServerChannel);
 
         mInputEventReceiver = new WindowPositionerEventReceiver(
                 mClientChannel, mService.mAnimationHandler.getLooper(),
@@ -267,8 +267,7 @@
         mDragApplicationHandle.dispatchingTimeoutNanos =
                 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
 
-        mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
-                display.getDisplayId());
+        mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, display.getDisplayId());
         mDragWindowHandle.name = TAG;
         mDragWindowHandle.token = mServerChannel.getToken();
         mDragWindowHandle.layer = mService.getDragLayerLocked();
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 75333c7..299b32c 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -48,7 +48,7 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 import static android.view.Display.DEFAULT_DISPLAY;
 
@@ -447,7 +447,7 @@
     }
 
     void cleanUpResourcesForDestroy() {
-        if (!mActivities.isEmpty()) {
+        if (hasChild()) {
             return;
         }
 
@@ -553,7 +553,7 @@
             // This method assumes that the task is already placed in the right stack.
             // we do not mess with that decision and we only do the resize!
 
-            Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + mTaskId);
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resizeTask_" + mTaskId);
 
             boolean updatedConfig = false;
             mTmpConfig.setTo(getResolvedOverrideConfiguration());
@@ -587,7 +587,7 @@
 
             saveLaunchingStateIfNeeded();
 
-            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             return kept;
         } finally {
             mAtmService.continueWindowLayout();
@@ -1095,7 +1095,7 @@
             // There are no non-finishing activities in the task.
             return null;
         }
-        return mActivities.get(rootActivityIndex);
+        return getChildAt(rootActivityIndex);
     }
 
     ActivityRecord getTopActivity() {
@@ -1103,8 +1103,8 @@
     }
 
     ActivityRecord getTopActivity(boolean includeOverlays) {
-        for (int i = mActivities.size() - 1; i >= 0; --i) {
-            final ActivityRecord r = mActivities.get(i);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final ActivityRecord r = getChildAt(i);
             if (r.finishing || (!includeOverlays && r.mTaskOverlay)) {
                 continue;
             }
@@ -1115,8 +1115,8 @@
 
     ActivityRecord topRunningActivityLocked() {
         if (mStack != null) {
-            for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
-                ActivityRecord r = mActivities.get(activityNdx);
+            for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                ActivityRecord r = getChildAt(activityNdx);
                 if (!r.finishing && r.okToShowLocked()) {
                     return r;
                 }
@@ -1126,8 +1126,8 @@
     }
 
     boolean isVisible() {
-        for (int i = mActivities.size() - 1; i >= 0; --i) {
-            final ActivityRecord r = mActivities.get(i);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final ActivityRecord r = getChildAt(i);
             if (r.visible) {
                 return true;
             }
@@ -1139,8 +1139,8 @@
      * Return true if any activities in this task belongs to input uid.
      */
     boolean containsAppUid(int uid) {
-        for (int i = mActivities.size() - 1; i >= 0; --i) {
-            final ActivityRecord r = mActivities.get(i);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final ActivityRecord r = getChildAt(i);
             if (r.getUid() == uid) {
                 return true;
             }
@@ -1150,8 +1150,8 @@
 
     void getAllRunningVisibleActivitiesLocked(ArrayList<ActivityRecord> outActivities) {
         if (mStack != null) {
-            for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
-                ActivityRecord r = mActivities.get(activityNdx);
+            for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                ActivityRecord r = getChildAt(activityNdx);
                 if (!r.finishing && r.okToShowLocked() && r.visibleIgnoringKeyguard) {
                     outActivities.add(r);
                 }
@@ -1161,8 +1161,8 @@
 
     ActivityRecord topRunningActivityWithStartingWindowLocked() {
         if (mStack != null) {
-            for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
-                ActivityRecord r = mActivities.get(activityNdx);
+            for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+                ActivityRecord r = getChildAt(activityNdx);
                 if (r.mStartingWindowState != STARTING_WINDOW_SHOWN
                         || r.finishing || !r.okToShowLocked()) {
                     continue;
@@ -1179,8 +1179,8 @@
      */
     void getNumRunningActivities(TaskActivitiesReport reportOut) {
         reportOut.reset();
-        for (int i = mActivities.size() - 1; i >= 0; --i) {
-            final ActivityRecord r = mActivities.get(i);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final ActivityRecord r = getChildAt(i);
             if (r.finishing) {
                 continue;
             }
@@ -1227,17 +1227,17 @@
     }
 
     void addActivityToTop(ActivityRecord r) {
-        addActivityAtIndex(mActivities.size(), r);
+        addActivityAtIndex(getChildCount(), r);
     }
 
     @Override
     /*@WindowConfiguration.ActivityType*/
     public int getActivityType() {
         final int applicationType = super.getActivityType();
-        if (applicationType != ACTIVITY_TYPE_UNDEFINED || mActivities.isEmpty()) {
+        if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) {
             return applicationType;
         }
-        return mActivities.get(0).getActivityType();
+        return getChildAt(0).getActivityType();
     }
 
     /**
@@ -1259,7 +1259,7 @@
             numFullscreen++;
         }
         // Only set this based on the first activity
-        if (mActivities.isEmpty()) {
+        if (!hasChild()) {
             if (r.getActivityType() == ACTIVITY_TYPE_UNDEFINED) {
                 // Normally non-standard activity type for the activity record will be set when the
                 // object is created, however we delay setting the standard application type until
@@ -1279,10 +1279,10 @@
             r.setActivityType(getActivityType());
         }
 
-        final int size = mActivities.size();
+        final int size = getChildCount();
 
         if (index == size && size > 0) {
-            final ActivityRecord top = mActivities.get(size - 1);
+            final ActivityRecord top = getChildAt(size - 1);
             if (top.mTaskOverlay) {
                 // Place below the task overlay activity since the overlay activity should always
                 // be on top.
@@ -1341,7 +1341,7 @@
             mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
         }
 
-        if (mActivities.isEmpty()) {
+        if (!hasChild()) {
             return !mReuseTask;
         }
         updateEffectiveIntent();
@@ -1355,8 +1355,8 @@
      */
     boolean onlyHasTaskOverlayActivities(boolean excludeFinishing) {
         int count = 0;
-        for (int i = mActivities.size() - 1; i >= 0; i--) {
-            final ActivityRecord r = mActivities.get(i);
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            final ActivityRecord r = getChildAt(i);
             if (excludeFinishing && r.finishing) {
                 continue;
             }
@@ -1372,7 +1372,7 @@
         // We will automatically remove the task either if it has explicitly asked for
         // this, or it is empty and has never contained an activity that got shown to
         // the user.
-        return autoRemoveRecents || (mActivities.isEmpty() && !hasBeenVisible);
+        return autoRemoveRecents || (!hasChild() && !hasBeenVisible);
     }
 
     /**
@@ -1380,9 +1380,9 @@
      * task starting at a specified index.
      */
     final void performClearTaskAtIndexLocked(int activityNdx, String reason) {
-        int numActivities = mActivities.size();
+        int numActivities = getChildCount();
         for ( ; activityNdx < numActivities; ++activityNdx) {
-            final ActivityRecord r = mActivities.get(activityNdx);
+            final ActivityRecord r = getChildAt(activityNdx);
             if (r.finishing) {
                 continue;
             }
@@ -1429,9 +1429,9 @@
      * or null if none was found.
      */
     final ActivityRecord performClearTaskLocked(ActivityRecord newR, int launchFlags) {
-        int numActivities = mActivities.size();
+        int numActivities = getChildCount();
         for (int activityNdx = numActivities - 1; activityNdx >= 0; --activityNdx) {
-            ActivityRecord r = mActivities.get(activityNdx);
+            ActivityRecord r = getChildAt(activityNdx);
             if (r.finishing) {
                 continue;
             }
@@ -1440,7 +1440,7 @@
                 final ActivityRecord ret = r;
 
                 for (++activityNdx; activityNdx < numActivities; ++activityNdx) {
-                    r = mActivities.get(activityNdx);
+                    r = getChildAt(activityNdx);
                     if (r.finishing) {
                         continue;
                     }
@@ -1591,8 +1591,8 @@
      */
     final ActivityRecord findActivityInHistoryLocked(ActivityRecord r) {
         final ComponentName realActivity = r.mActivityComponent;
-        for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
-            ActivityRecord candidate = mActivities.get(activityNdx);
+        for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+            ActivityRecord candidate = getChildAt(activityNdx);
             if (candidate.finishing) {
                 continue;
             }
@@ -1609,12 +1609,12 @@
         // Traverse upwards looking for any break between main task activities and
         // utility activities.
         int activityNdx;
-        final int numActivities = mActivities.size();
+        final int numActivities = getChildCount();
         final boolean relinquish = numActivities != 0 &&
-                (mActivities.get(0).info.flags & FLAG_RELINQUISH_TASK_IDENTITY) != 0;
+                (getChildAt(0).info.flags & FLAG_RELINQUISH_TASK_IDENTITY) != 0;
         for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
                 ++activityNdx) {
-            final ActivityRecord r = mActivities.get(activityNdx);
+            final ActivityRecord r = getChildAt(activityNdx);
             if (relinquish && (r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
                 // This will be the top activity for determining taskDescription. Pre-inc to
                 // overcome initial decrement below.
@@ -1642,7 +1642,7 @@
             boolean navigationBarContrastWhenTransparent = false;
             boolean topActivity = true;
             for (--activityNdx; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = mActivities.get(activityNdx);
+                final ActivityRecord r = getChildAt(activityNdx);
                 if (r.mTaskOverlay) {
                     continue;
                 }
@@ -1697,9 +1697,9 @@
      */
     int findRootIndex(boolean effectiveRoot) {
         int effectiveNdx = -1;
-        final int topActivityNdx = mActivities.size() - 1;
+        final int topActivityNdx = getChildCount() - 1;
         for (int activityNdx = 0; activityNdx <= topActivityNdx; ++activityNdx) {
-            final ActivityRecord r = mActivities.get(activityNdx);
+            final ActivityRecord r = getChildAt(activityNdx);
             if (r.finishing) {
                 continue;
             }
@@ -1720,7 +1720,7 @@
             // But we still want to update the intent, so let's use the bottom activity.
             effectiveRootIndex = 0;
         }
-        final ActivityRecord r = mActivities.get(effectiveRootIndex);
+        final ActivityRecord r = getChildAt(effectiveRootIndex);
         setIntent(r);
 
         // Update the task description when the activities change
@@ -2289,8 +2289,8 @@
     }
 
     void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
-        for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
-            final ActivityRecord r = mActivities.get(activityNdx);
+        for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+            final ActivityRecord r = getChildAt(activityNdx);
             if (r.visible) {
                 r.showStartingWindow(null /* prev */, false /* newTask */, taskSwitch);
             }
@@ -2462,7 +2462,7 @@
             sb.append(" StackId=");
             sb.append(getStackId());
             sb.append(" sz=");
-            sb.append(mActivities.size());
+            sb.append(getChildCount());
             sb.append('}');
             return sb.toString();
         }
@@ -2495,8 +2495,8 @@
         final long token = proto.start(fieldId);
         super.writeToProto(proto, CONFIGURATION_CONTAINER, logLevel);
         proto.write(ID, mTaskId);
-        for (int i = mActivities.size() - 1; i >= 0; i--) {
-            ActivityRecord activity = mActivities.get(i);
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            ActivityRecord activity = getChildAt(i);
             activity.writeToProto(proto, ACTIVITIES);
         }
         proto.write(STACK_ID, mStack.mStackId);
@@ -2607,10 +2607,9 @@
             out.endTag(null, TAG_INTENT);
         }
 
-        final ArrayList<ActivityRecord> activities = mActivities;
-        final int numActivities = activities.size();
+        final int numActivities = getChildCount();
         for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
-            final ActivityRecord r = activities.get(activityNdx);
+            final ActivityRecord r = getChildAt(activityNdx);
             if (r.info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || !r.isPersistable() ||
                     ((r.intent.getFlags() & FLAG_ACTIVITY_NEW_DOCUMENT
                             | FLAG_ACTIVITY_RETAIN_IN_RECENTS) == FLAG_ACTIVITY_NEW_DOCUMENT) &&
diff --git a/services/core/java/com/android/server/wm/WindowHashMap.java b/services/core/java/com/android/server/wm/WindowHashMap.java
deleted file mode 100644
index 49bba41..0000000
--- a/services/core/java/com/android/server/wm/WindowHashMap.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import android.os.IBinder;
-
-import java.util.HashMap;
-
-/**
- * Subclass of HashMap such that we can instruct the compiler to boost our thread priority when
- * locking this class. See makefile.
- */
-class WindowHashMap extends HashMap<IBinder, WindowState> {
-}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index f4b7672..0cb4826 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -514,6 +514,13 @@
     public abstract void showImePostLayout(IBinder imeTargetWindowToken);
 
     /**
+     * Hide IME using imeTargetWindow when requested.
+     *
+     * @param displayId on which IME is shown
+     */
+    public abstract void hideIme(int displayId);
+
+    /**
      * Tell window manager about a package that should not be running with high refresh rate
      * setting until removeNonHighRefreshRatePackage is called for the same package.
      *
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6f9f2c0..c485280 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -236,6 +236,7 @@
 import android.view.SurfaceSession;
 import android.view.View;
 import android.view.WindowContentFrameStats;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.RemoveContentMode;
@@ -518,7 +519,10 @@
     final ArraySet<Session> mSessions = new ArraySet<>();
 
     /** Mapping from an IWindow IBinder to the server's Window object. */
-    final WindowHashMap mWindowMap = new WindowHashMap();
+    final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
+
+    /** Mapping from an InputWindowHandle token to the server's Window object. */
+    final HashMap<IBinder, WindowState> mInputToWindowMap = new HashMap<>();
 
     /** Global service lock used by the package the owns this service. */
     final WindowManagerGlobalLock mGlobalLock;
@@ -1578,7 +1582,6 @@
 
             win.attach();
             mWindowMap.put(client.asBinder(), win);
-
             win.initAppOpsState();
 
             final boolean suspended = mPmInternal.isPackageSuspended(win.getOwningPackage(),
@@ -7310,6 +7313,16 @@
         }
 
         @Override
+        public void hideIme(int displayId) {
+            synchronized (mGlobalLock) {
+                final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                if (dc != null && dc.mInputMethodTarget != null) {
+                    dc.mInputMethodTarget.hideInsets(WindowInsets.Type.ime(), true /* fromIme */);
+                }
+            }
+        }
+
+        @Override
         public boolean isUidAllowedOnDisplay(int displayId, int uid) {
             if (displayId == Display.DEFAULT_DISPLAY) {
                 return true;
@@ -7592,7 +7605,7 @@
     }
 
     private void onPointerDownOutsideFocusLocked(IBinder touchedToken) {
-        final WindowState touchedWindow = windowForClientLocked(null, touchedToken, false);
+        final WindowState touchedWindow = mInputToWindowMap.get(touchedToken);
         if (touchedWindow == null || !touchedWindow.canReceiveKeys()) {
             return;
         }
@@ -7665,9 +7678,9 @@
         clientChannel.transferTo(outInputChannel);
         clientChannel.dispose();
 
-        mInputManager.registerInputChannel(inputChannel, null /* generate new token */);
+        mInputManager.registerInputChannel(inputChannel);
 
-        InputWindowHandle h = new InputWindowHandle(null, null, displayId);
+        InputWindowHandle h = new InputWindowHandle(null, displayId);
         h.token = inputChannel.getToken();
         h.name = name;
         h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 56e08b2..7ff9b70 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -820,8 +820,7 @@
         mLastRequestedHeight = 0;
         mLayer = 0;
         mInputWindowHandle = new InputWindowHandle(
-                mAppToken != null ? mAppToken.mInputApplicationHandle : null, c,
-                    getDisplayId());
+                mAppToken != null ? mAppToken.mInputApplicationHandle : null, getDisplayId());
     }
 
     void attach() {
@@ -2191,7 +2190,9 @@
         InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
         mInputChannel = inputChannels[0];
         mClientChannel = inputChannels[1];
-        mInputWindowHandle.token = mClient.asBinder();
+        mWmService.mInputManager.registerInputChannel(mInputChannel);
+        mClientChannel.setToken(mInputChannel.getToken());
+        mInputWindowHandle.token = mInputChannel.getToken();
         if (outInputChannel != null) {
             mClientChannel.transferTo(outInputChannel);
             mClientChannel.dispose();
@@ -2202,7 +2203,7 @@
             // Create dummy event receiver that simply reports all events as handled.
             mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
         }
-        mWmService.mInputManager.registerInputChannel(mInputChannel, mClient.asBinder());
+        mWmService.mInputToWindowMap.put(mInputWindowHandle.token, this);
     }
 
     void disposeInputChannel() {
@@ -2223,6 +2224,7 @@
             mClientChannel = null;
         }
         mWmService.mKeyInterceptionInfoForToken.remove(mInputWindowHandle.token);
+        mWmService.mInputToWindowMap.remove(mInputWindowHandle.token);
         mInputWindowHandle.token = null;
     }
 
@@ -3375,6 +3377,15 @@
         }
     }
 
+    @Override
+    public void hideInsets(@InsetType int types, boolean fromIme) {
+        try {
+            mClient.hideInsets(types, fromIme);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to deliver showInsets", e);
+        }
+    }
+
     Rect getBackdropFrame(Rect frame) {
         // When the task is docked, we send fullscreen sized backDropFrame as soon as resizing
         // start even if we haven't received the relayout window, so that the client requests
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1328273..eac372f 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1348,13 +1348,16 @@
         // frozen, there is no reason to animate and it can cause strange
         // artifacts when we unfreeze the display if some different animation
         // is running.
-        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#applyAnimationLocked");
         if (mWin.mToken.okToAnimate()) {
-            int anim = mWin.getDisplayContent().getDisplayPolicy().selectAnimationLw(mWin, transit);
+            int anim = mWin.getDisplayContent().getDisplayPolicy().selectAnimation(mWin, transit);
             int attr = -1;
             Animation a = null;
-            if (anim != 0) {
-                a = anim != -1 ? AnimationUtils.loadAnimation(mContext, anim) : null;
+            if (anim != DisplayPolicy.ANIMATION_STYLEABLE) {
+                if (anim != DisplayPolicy.ANIMATION_NONE) {
+                    Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#loadAnimation");
+                    a = AnimationUtils.loadAnimation(mContext, anim);
+                    Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+                }
             } else {
                 switch (transit) {
                     case WindowManagerPolicy.TRANSIT_ENTER:
@@ -1384,7 +1387,9 @@
                     + " isEntrance=" + isEntrance + " Callers " + Debug.getCallers(3));
             if (a != null) {
                 if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
+                Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#startAnimation");
                 mWin.startAnimation(a);
+                Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                 mAnimationIsEntrance = isEntrance;
             }
         } else {
@@ -1395,7 +1400,6 @@
             mWin.getDisplayContent().adjustForImeIfNeeded();
         }
 
-        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
         return mWin.isAnimating();
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 56f6d4b..0cfdebc 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -23,7 +23,6 @@
 import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
 
 import android.os.Debug;
-import android.os.Trace;
 import android.util.Slog;
 
 import java.io.PrintWriter;
@@ -52,15 +51,19 @@
     /** The number of layout requests when deferring. */
     private int mDeferredRequests;
 
-    private final Runnable mPerformSurfacePlacement;
-
-    public WindowSurfacePlacer(WindowManagerService service) {
-        mService = service;
-        mPerformSurfacePlacement = () -> {
+    private class Traverser implements Runnable {
+        @Override
+        public void run() {
             synchronized (mService.mGlobalLock) {
                 performSurfacePlacement();
             }
-        };
+        }
+    }
+
+    private final Traverser mPerformSurfacePlacement = new Traverser();
+
+    WindowSurfacePlacer(WindowManagerService service) {
+        mService = service;
     }
 
     /**
@@ -152,7 +155,6 @@
             return;
         }
 
-        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout");
         mInLayout = true;
 
         boolean recoveringMemory = false;
@@ -198,8 +200,6 @@
             mInLayout = false;
             Slog.wtf(TAG, "Unhandled exception while laying out windows", e);
         }
-
-        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     void debugLayoutRepeats(final String msg, int pendingLayoutChanges) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index fb2fdab..dd2629d 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -201,8 +201,7 @@
 
     void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
 
-    status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel,
-            int32_t displayId);
+    status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
     status_t registerInputMonitor(JNIEnv* env, const sp<InputChannel>& inputChannel,
             int32_t displayId, bool isGestureMonitor);
     status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
@@ -435,10 +434,9 @@
 }
 
 status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
-        const sp<InputChannel>& inputChannel, int32_t displayId) {
+        const sp<InputChannel>& inputChannel) {
     ATRACE_CALL();
-    return mInputManager->getDispatcher()->registerInputChannel(
-            inputChannel, displayId);
+    return mInputManager->getDispatcher()->registerInputChannel(inputChannel);
 }
 
 status_t NativeInputManager::registerInputMonitor(JNIEnv* /* env */,
@@ -1405,7 +1403,7 @@
 }
 
 static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
-        jlong ptr, jobject inputChannelObj, jint displayId) {
+        jlong ptr, jobject inputChannelObj) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
 
     sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
@@ -1415,7 +1413,7 @@
         return;
     }
 
-    status_t status = im->registerInputChannel(env, inputChannel, displayId);
+    status_t status = im->registerInputChannel(env, inputChannel);
 
     if (status) {
         std::string message;
@@ -1757,7 +1755,7 @@
     { "nativeHasKeys", "(JII[I[Z)Z",
             (void*) nativeHasKeys },
     { "nativeRegisterInputChannel",
-            "(JLandroid/view/InputChannel;I)V",
+            "(JLandroid/view/InputChannel;)V",
             (void*) nativeRegisterInputChannel },
     { "nativeRegisterInputMonitor",
             "(JLandroid/view/InputChannel;IZ)V",
diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp
index 9ceb770..906b568 100644
--- a/services/core/jni/com_android_server_security_VerityUtils.cpp
+++ b/services/core/jni/com_android_server_security_VerityUtils.cpp
@@ -17,6 +17,8 @@
 #define LOG_TAG "VerityUtils"
 
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "jni.h"
 #include <utils/Log.h>
 
@@ -72,11 +74,17 @@
 namespace {
 
 int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath, jbyteArray signature) {
-    const char* path = env->GetStringUTFChars(filePath, nullptr);
-    ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC));
-    env->ReleaseStringUTFChars(filePath, path);
+    ScopedUtfChars path(env, filePath);
+    if (path.c_str() == nullptr) {
+        return EINVAL;
+    }
+    ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
     if (rfd.get() < 0) {
-      return errno;
+        return errno;
+    }
+    ScopedByteArrayRO signature_bytes(env, signature);
+    if (signature_bytes.get() == nullptr) {
+        return EINVAL;
     }
 
     fsverity_enable_arg arg = {};
@@ -85,11 +93,11 @@
     arg.block_size = 4096;
     arg.salt_size = 0;
     arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr);
-    arg.sig_size = env->GetArrayLength(signature);
-    arg.sig_ptr = reinterpret_cast<uintptr_t>(signature);
+    arg.sig_size = signature_bytes.size();
+    arg.sig_ptr = reinterpret_cast<uintptr_t>(signature_bytes.get());
 
     if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) {
-      return errno;
+        return errno;
     }
     return 0;
 }
@@ -101,14 +109,16 @@
     fsverity_digest *data = reinterpret_cast<fsverity_digest *>(&bytes);
     data->digest_size = kSha256Bytes;  // the only input/output parameter
 
-    const char* path = env->GetStringUTFChars(filePath, nullptr);
-    ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC));
-    env->ReleaseStringUTFChars(filePath, path);
+    ScopedUtfChars path(env, filePath);
+    if (path.c_str() == nullptr) {
+        return EINVAL;
+    }
+    ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
     if (rfd.get() < 0) {
-      return errno;
+        return errno;
     }
     if (ioctl(rfd.get(), FS_IOC_MEASURE_VERITY, data) < 0) {
-      return errno;
+        return errno;
     }
     return 0;
 }
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index 47790ce..91c05a8 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -5,4 +5,13 @@
     libs: [
         "services.core",
     ],
+
+    plugins: [
+        "compat-changeid-annotation-processor",
+    ],
 }
+
+platform_compat_config {
+    name: "services-devicepolicy-platform-compat-config",
+    src: ":services.devicepolicy",
+}
\ No newline at end of file
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9569ac8..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;
@@ -137,6 +138,8 @@
 import android.app.backup.IBackupManager;
 import android.app.trust.TrustManager;
 import android.app.usage.UsageStatsManagerInternal;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -235,6 +238,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
@@ -251,6 +255,8 @@
 import com.android.internal.util.XmlUtils;
 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;
@@ -494,6 +500,22 @@
     private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
     private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
 
+    /**
+     * For admin apps targeting R+, throw when the app sets password requirement
+     * that is not taken into account at given quality. For example when quality is set
+     * to {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't make sense to
+     * require certain password length. If the intent is to require a password of certain length
+     * having at least NUMERIC quality, the admin should first call
+     * {@link #setPasswordQuality(ComponentName, int, boolean)} and only then call
+     * {@link #setPasswordMinimumLength(ComponentName, int, boolean)}.
+     *
+     * <p>Conversely when an admin app targeting R+ lowers password quality, those
+     * requirements that stop making sense are reset to default values.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    private static final long ADMIN_APP_PASSWORD_COMPLEXITY = 123562444L;
+
     final Context mContext;
     final Injector mInjector;
     final IPackageManager mIPackageManager;
@@ -506,6 +528,7 @@
     private final LockSettingsInternal mLockSettingsInternal;
     private final DeviceAdminServiceController mDeviceAdminServiceController;
     private final OverlayPackagesProvider mOverlayPackagesProvider;
+    private final IPlatformCompat mIPlatformCompat;
 
     private final DevicePolicyCacheImpl mPolicyCache = new DevicePolicyCacheImpl();
     private final DeviceStateCacheImpl mStateCache = new DeviceStateCacheImpl();
@@ -968,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;
@@ -1115,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) {
@@ -1383,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(
@@ -1668,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=");
@@ -1984,6 +1996,11 @@
             return LocalServices.getService(LockSettingsInternal.class);
         }
 
+        IPlatformCompat getIPlatformCompat() {
+            return IPlatformCompat.Stub.asInterface(
+                    ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        }
+
         boolean hasUserSetupCompleted(DevicePolicyData userData) {
             return userData.mUserSetupComplete;
         }
@@ -2222,6 +2239,7 @@
         mUsageStatsManagerInternal = Preconditions.checkNotNull(
                 injector.getUsageStatsManagerInternal());
         mIPackageManager = Preconditions.checkNotNull(injector.getIPackageManager());
+        mIPlatformCompat = Preconditions.checkNotNull(injector.getIPlatformCompat());
         mIPermissionManager = Preconditions.checkNotNull(injector.getIPermissionManager());
         mTelephonyManager = Preconditions.checkNotNull(injector.getTelephonyManager());
 
@@ -4135,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);
             }
@@ -4156,23 +4174,33 @@
                 .write();
     }
 
+    private boolean passwordQualityInvocationOrderCheckEnabled(String packageName, int userId) {
+        try {
+            return mIPlatformCompat.isChangeEnabledByPackageName(ADMIN_APP_PASSWORD_COMPLEXITY,
+                    packageName);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e);
+        }
+        return getTargetSdk(packageName, userId) > Build.VERSION_CODES.Q;
+    }
+
     /**
      * For admins targeting R+ reset various password constraints to default values when quality is
      * set to a value that makes those constraints that have no effect.
      */
     private void resetInactivePasswordRequirementsIfRPlus(int userId, ActiveAdmin admin) {
-        if (getTargetSdk(admin.info.getPackageName(), userId) > Build.VERSION_CODES.Q) {
-            final PasswordMetrics metrics = admin.minimumPasswordMetrics;
-            if (metrics.quality < PASSWORD_QUALITY_NUMERIC) {
-                metrics.length = ActiveAdmin.DEF_MINIMUM_PASSWORD_LENGTH;
+        if (passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(), userId)) {
+            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;
             }
         }
     }
@@ -4237,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.
@@ -4246,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;
@@ -4307,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)
@@ -4325,8 +4353,9 @@
 
     private void ensureMinimumQuality(
             int userId, ActiveAdmin admin, int minimumQuality, String operation) {
-        if (admin.minimumPasswordMetrics.quality < minimumQuality
-                && getTargetSdk(admin.info.getPackageName(), userId) > Build.VERSION_CODES.Q) {
+        if (admin.mPasswordPolicy.quality < minimumQuality
+                && passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(),
+                userId)) {
             throw new IllegalStateException(String.format(
                     "password quality should be at least %d for %s", minimumQuality, operation));
         }
@@ -4335,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
@@ -4564,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)
@@ -4582,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
@@ -4594,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)
@@ -4612,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
@@ -4626,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)
@@ -4644,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
@@ -4658,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)
@@ -4676,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
@@ -4690,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)
@@ -4708,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
@@ -4723,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)
@@ -4741,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);
     }
 
     /**
@@ -4777,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) {
@@ -4792,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;
         }
     }
 
@@ -4856,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
@@ -5132,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);
@@ -5222,28 +5205,20 @@
         // back in to the service.
         final long ident = mInjector.binderClearCallingIdentity();
         final boolean result;
+        final LockscreenCredential newCredential =
+                LockscreenCredential.createPasswordOrNone(password);
         try {
             if (token == null) {
                 // This is the legacy reset password for DPM. Here we want to be able to override
                 // the old device password without necessarily knowing it.
-                if (!TextUtils.isEmpty(password)) {
-                    mLockPatternUtils.saveLockPassword(password.getBytes(), null, quality,
-                            userHandle, /*allowUntrustedChange */true);
-                } else {
-                    mLockPatternUtils.clearLock(null, userHandle,
-                            /*allowUntrustedChange */ true);
-                }
+                mLockPatternUtils.setLockCredential(
+                        newCredential,
+                        LockscreenCredential.createNone(),
+                        userHandle, /*allowUntrustedChange */true);
                 result = true;
             } else {
-                if (!TextUtils.isEmpty(password)) {
-                    result = mLockPatternUtils.setLockCredentialWithToken(password.getBytes(),
-                            LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                            quality, tokenHandle, token, userHandle);
-                } else {
-                    result = mLockPatternUtils.setLockCredentialWithToken(null,
-                            LockPatternUtils.CREDENTIAL_TYPE_NONE,
-                            quality, tokenHandle, token, userHandle);
-                }
+                result = mLockPatternUtils.setLockCredentialWithToken(newCredential, tokenHandle,
+                        token, userHandle);
             }
             boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0;
             if (requireEntry) {
@@ -11574,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);
@@ -14196,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/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
index 0838fbc..7cfbcc8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
@@ -51,7 +51,7 @@
     static final long REMOTE_BUGREPORT_TIMEOUT_MILLIS = 10 * DateUtils.MINUTE_IN_MILLIS;
 
     static final String CTL_STOP = "ctl.stop";
-    static final String REMOTE_BUGREPORT_SERVICE = "bugreportremote";
+    static final String REMOTE_BUGREPORT_SERVICE = "bugreportd";
 
     static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4cf98d3..88859a7 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -921,7 +921,6 @@
                 false);
         boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
                 false);
-        boolean disableSlices = SystemProperties.getBoolean("config.disable_slices", false);
         boolean enableLeftyService = SystemProperties.getBoolean("config.enable_lefty", false);
 
         boolean isEmulator = SystemProperties.get("ro.kernel.qemu").equals("1");
@@ -1856,7 +1855,7 @@
             t.traceEnd();
         }
 
-        if (!disableSlices) {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) {
             t.traceBegin("StartSliceManagerService");
             mSystemServiceManager.startService(SLICE_MANAGER_SERVICE_CLASS);
             t.traceEnd();
diff --git a/services/print/java/com/android/server/print/TEST_MAPPING b/services/print/java/com/android/server/print/TEST_MAPPING
new file mode 100644
index 0000000..4fa8822
--- /dev/null
+++ b/services/print/java/com/android/server/print/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsPrintTestCases",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index edf82ee..597d337 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -41,12 +41,14 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.testing.DexmakerShareClassLoaderRule;
 import android.view.Display;
 
 import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -65,9 +67,14 @@
             "com.android.server.accessibility", "AccessibilityServiceConnectionTest");
     static final int SERVICE_ID = 42;
 
+    // Mock package-private AccessibilityUserState class
+    @Rule
+    public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+            new DexmakerShareClassLoaderRule();
+
     AccessibilityServiceConnection mConnection;
 
-    @Mock AccessibilityManagerService.UserState mMockUserState;
+    @Mock AccessibilityUserState mMockUserState;
     @Mock Context mMockContext;
     @Mock AccessibilityServiceInfo mMockServiceInfo;
     @Mock ResolveInfo mMockResolveInfo;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
new file mode 100644
index 0000000..9180054
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -0,0 +1,297 @@
+/*
+ * 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.accessibility;
+
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.testing.DexmakerShareClassLoaderRule;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for AccessibilityUserState */
+public class AccessibilityUserStateTest {
+
+    private static final ComponentName COMPONENT_NAME =
+            new ComponentName("com.android.server.accessibility", "AccessibilityUserStateTest");
+
+    // Values of setting key SHOW_IME_WITH_HARD_KEYBOARD
+    private static final int STATE_HIDE_IME = 0;
+    private static final int STATE_SHOW_IME = 1;
+
+    private static final int USER_ID = 42;
+
+    // Mock package-private class AccessibilityServiceConnection
+    @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+            new DexmakerShareClassLoaderRule();
+
+    @Mock private AccessibilityServiceInfo mMockServiceInfo;
+
+    @Mock private AccessibilityServiceConnection mMockConnection;
+
+    @Mock private AccessibilityUserState.ServiceInfoChangeListener mMockListener;
+
+    @Mock private Context mContext;
+
+    private MockContentResolver mMockResolver;
+
+    private AccessibilityUserState mUserState;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeSettingsProvider.clearSettingsProvider();
+        mMockResolver = new MockContentResolver();
+        mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        when(mContext.getContentResolver()).thenReturn(mMockResolver);
+        when(mMockServiceInfo.getComponentName()).thenReturn(COMPONENT_NAME);
+        when(mMockConnection.getServiceInfo()).thenReturn(mMockServiceInfo);
+
+        mUserState = new AccessibilityUserState(USER_ID, mContext, mMockListener);
+    }
+
+    @After
+    public void tearDown() {
+        FakeSettingsProvider.clearSettingsProvider();
+    }
+
+    @Test
+    public void onSwitchToAnotherUser_userStateClearedNonDefaultValues() {
+        mUserState.getBoundServicesLocked().add(mMockConnection);
+        mUserState.getBindingServicesLocked().add(COMPONENT_NAME);
+        mUserState.setLastSentClientStateLocked(
+                STATE_FLAG_ACCESSIBILITY_ENABLED
+                        | STATE_FLAG_TOUCH_EXPLORATION_ENABLED
+                        | STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED);
+        mUserState.setNonInteractiveUiTimeoutLocked(30);
+        mUserState.setInteractiveUiTimeoutLocked(30);
+        mUserState.mEnabledServices.add(COMPONENT_NAME);
+        mUserState.mTouchExplorationGrantedServices.add(COMPONENT_NAME);
+        mUserState.setTouchExplorationEnabledLocked(true);
+        mUserState.setDisplayMagnificationEnabledLocked(true);
+        mUserState.setNavBarMagnificationEnabledLocked(true);
+        mUserState.setServiceAssignedToAccessibilityButtonLocked(COMPONENT_NAME);
+        mUserState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(true);
+        mUserState.setAutoclickEnabledLocked(true);
+        mUserState.setUserNonInteractiveUiTimeoutLocked(30);
+        mUserState.setUserInteractiveUiTimeoutLocked(30);
+
+        mUserState.onSwitchToAnotherUserLocked();
+
+        verify(mMockConnection).unbindLocked();
+        assertTrue(mUserState.getBoundServicesLocked().isEmpty());
+        assertTrue(mUserState.getBindingServicesLocked().isEmpty());
+        assertEquals(-1, mUserState.getLastSentClientStateLocked());
+        assertEquals(0, mUserState.getNonInteractiveUiTimeoutLocked());
+        assertEquals(0, mUserState.getInteractiveUiTimeoutLocked());
+        assertTrue(mUserState.mEnabledServices.isEmpty());
+        assertTrue(mUserState.mTouchExplorationGrantedServices.isEmpty());
+        assertFalse(mUserState.isTouchExplorationEnabledLocked());
+        assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
+        assertFalse(mUserState.isNavBarMagnificationEnabledLocked());
+        assertNull(mUserState.getServiceAssignedToAccessibilityButtonLocked());
+        assertFalse(mUserState.isNavBarMagnificationAssignedToAccessibilityButtonLocked());
+        assertFalse(mUserState.isAutoclickEnabledLocked());
+        assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
+        assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
+    }
+
+    @Test
+    public void addService_connectionAlreadyAdded_notAddAgain() {
+        mUserState.getBoundServicesLocked().add(mMockConnection);
+
+        mUserState.addServiceLocked(mMockConnection);
+
+        verify(mMockConnection, never()).onAdded();
+    }
+
+    @Test
+    public void addService_connectionNotYetAddedToBoundService_addAndNotifyServices() {
+        when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
+
+        mUserState.addServiceLocked(mMockConnection);
+
+        verify(mMockConnection).onAdded();
+        assertTrue(mUserState.getBoundServicesLocked().contains(mMockConnection));
+        assertEquals(mMockConnection, mUserState.mComponentNameToServiceMap.get(COMPONENT_NAME));
+        verify(mMockListener).onServiceInfoChangedLocked(eq(mUserState));
+    }
+
+    @Test
+    public void reconcileSoftKeyboardMode_whenStateNotMatchSettings_setBothDefault() {
+        // When soft kb show mode is hidden in settings and is auto in state.
+        putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+                SHOW_MODE_HIDDEN, USER_ID);
+
+        mUserState.reconcileSoftKeyboardModeWithSettingsLocked();
+
+        assertEquals(SHOW_MODE_AUTO, mUserState.getSoftKeyboardShowModeLocked());
+        assertEquals(SHOW_MODE_AUTO, getSecureIntForUser(
+                Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, USER_ID));
+        assertNull(mUserState.getServiceChangingSoftKeyboardModeLocked());
+    }
+
+    @Test
+    public void
+            reconcileSoftKeyboardMode_stateIgnoreHardKb_settingsShowImeHardKb_setAutoOverride() {
+        // When show mode is ignore hard kb without original hard kb value
+        // and show ime with hard kb is hide
+        putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+                SHOW_MODE_IGNORE_HARD_KEYBOARD, USER_ID);
+        mUserState.setSoftKeyboardModeLocked(SHOW_MODE_IGNORE_HARD_KEYBOARD, COMPONENT_NAME);
+        putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+                STATE_HIDE_IME, USER_ID);
+
+        mUserState.reconcileSoftKeyboardModeWithSettingsLocked();
+
+        assertEquals(SHOW_MODE_AUTO | SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN,
+                getSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, USER_ID));
+        assertNull(mUserState.getServiceChangingSoftKeyboardModeLocked());
+    }
+
+    @Test
+    public void removeService_serviceChangingSoftKeyboardMode_removeAndSetSoftKbModeAuto() {
+        mUserState.setServiceChangingSoftKeyboardModeLocked(COMPONENT_NAME);
+        mUserState.mComponentNameToServiceMap.put(COMPONENT_NAME, mMockConnection);
+        mUserState.setSoftKeyboardModeLocked(SHOW_MODE_HIDDEN, COMPONENT_NAME);
+
+        mUserState.removeServiceLocked(mMockConnection);
+
+        assertFalse(mUserState.getBoundServicesLocked().contains(mMockConnection));
+        verify(mMockConnection).onRemoved();
+        assertEquals(SHOW_MODE_AUTO, mUserState.getSoftKeyboardShowModeLocked());
+        assertNull(mUserState.mComponentNameToServiceMap.get(COMPONENT_NAME));
+        verify(mMockListener).onServiceInfoChangedLocked(eq(mUserState));
+    }
+
+    @Test
+    public void serviceDisconnected_removeServiceAndAddToBinding() {
+        when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
+        mUserState.addServiceLocked(mMockConnection);
+
+        mUserState.serviceDisconnectedLocked(mMockConnection);
+
+        assertFalse(mUserState.getBoundServicesLocked().contains(mMockConnection));
+        assertTrue(mUserState.getBindingServicesLocked().contains(COMPONENT_NAME));
+    }
+
+    @Test
+    public void setSoftKeyboardMode_withInvalidShowMode_shouldKeepDefaultAuto() {
+        final int invalidShowMode = SHOW_MODE_HIDDEN | SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE;
+
+        assertFalse(mUserState.setSoftKeyboardModeLocked(invalidShowMode, null));
+
+        assertEquals(SHOW_MODE_AUTO, mUserState.getSoftKeyboardShowModeLocked());
+    }
+
+    @Test
+    public void setSoftKeyboardMode_newModeSameWithCurrentState_returnTrue() {
+        when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
+        mUserState.addServiceLocked(mMockConnection);
+
+        assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null));
+    }
+
+    @Test
+    public void setSoftKeyboardMode_withIgnoreHardKb_whenHardKbOverridden_returnFalseAdNoChange() {
+        putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+                SHOW_MODE_AUTO | SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN, USER_ID);
+
+        assertFalse(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_IGNORE_HARD_KEYBOARD, null));
+
+        assertEquals(SHOW_MODE_AUTO, mUserState.getSoftKeyboardShowModeLocked());
+    }
+
+    @Test
+    public void
+            setSoftKeyboardMode_withIgnoreHardKb_whenShowImeWithHardKb_setOriginalHardKbValue() {
+        putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, STATE_SHOW_IME, USER_ID);
+
+        assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_IGNORE_HARD_KEYBOARD, null));
+
+        assertEquals(SHOW_MODE_IGNORE_HARD_KEYBOARD | SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE,
+                getSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, USER_ID));
+    }
+
+    @Test
+    public void setSoftKeyboardMode_whenCurrentIgnoreHardKb_shouldSetShowImeWithHardKbValue() {
+        mUserState.setSoftKeyboardModeLocked(SHOW_MODE_IGNORE_HARD_KEYBOARD, COMPONENT_NAME);
+        putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, STATE_HIDE_IME, USER_ID);
+        putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+                SHOW_MODE_IGNORE_HARD_KEYBOARD | SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE, USER_ID);
+
+        assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null));
+
+        assertEquals(STATE_SHOW_IME, getSecureIntForUser(
+                Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, USER_ID));
+    }
+
+    @Test
+    public void setSoftKeyboardMode_withRequester_shouldUpdateInternalStateAndSettingsAsIs() {
+        assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_HIDDEN, COMPONENT_NAME));
+
+        assertEquals(SHOW_MODE_HIDDEN, mUserState.getSoftKeyboardShowModeLocked());
+        assertEquals(SHOW_MODE_HIDDEN, getSecureIntForUser(
+                Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, USER_ID));
+        assertEquals(COMPONENT_NAME, mUserState.getServiceChangingSoftKeyboardModeLocked());
+    }
+
+    @Test
+    public void setSoftKeyboardMode_shouldNotifyBoundService() {
+        mUserState.addServiceLocked(mMockConnection);
+
+        assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_HIDDEN, COMPONENT_NAME));
+
+        verify(mMockConnection).notifySoftKeyboardShowModeChangedLocked(eq(SHOW_MODE_HIDDEN));
+    }
+
+    private int getSecureIntForUser(String key, int userId) {
+        return Settings.Secure.getIntForUser(mMockResolver, key, -1, userId);
+    }
+
+    private void putSecureIntForUser(String key, int value, int userId) {
+        Settings.Secure.putIntForUser(mMockResolver, key, value, userId);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index deb6f71..8da927d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -56,7 +56,6 @@
 
     MessageCapturingHandler mMessageCapturingHandler;
 
-    @Mock AccessibilityManagerService.UserState mMockUserState;
     @Mock Context mMockContext;
     @Mock AccessibilityServiceInfo mMockServiceInfo;
     @Mock ResolveInfo mMockResolveInfo;
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 9a1fd9c..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;
 
@@ -93,7 +95,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
-import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener;
@@ -4266,9 +4268,9 @@
         assertTrue(dpm.isResetPasswordTokenActive(admin1));
 
         // test reset password with token
-        when(getServices().lockPatternUtils.setLockCredentialWithToken(eq(password.getBytes()),
-                eq(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD),
-                eq(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), eq(handle), eq(token),
+        when(getServices().lockPatternUtils.setLockCredentialWithToken(
+                eq(LockscreenCredential.createPassword(password)),
+                eq(handle), eq(token),
                 eq(UserHandle.USER_SYSTEM)))
                 .thenReturn(true);
         assertTrue(dpm.resetPasswordWithToken(admin1, password, token, 0));
@@ -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/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
index 1fcd0ef..baf1ed0 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
@@ -127,4 +127,57 @@
 
         assertEquals(rule1, matchedRule);
     }
+
+    @Test
+    public void testMatchRules_validForm() {
+        OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.AND, Arrays.asList(
+                new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+                        PACKAGE_NAME_1),
+                new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
+                        AtomicFormula.Operator.EQ,
+                        APP_CERTIFICATE)));
+        Rule rule = new Rule(
+                openFormula, Rule.Effect.DENY);
+
+        Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
+                APP_INSTALL_METADATA);
+
+        assertEquals(rule, matchedRule);
+    }
+
+    @Test
+    public void testMatchRules_ruleNotInDNF() {
+        OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.OR, Arrays.asList(
+                new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+                        PACKAGE_NAME_1),
+                new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
+                        AtomicFormula.Operator.EQ,
+                        APP_CERTIFICATE)));
+        Rule rule = new Rule(
+                openFormula, Rule.Effect.DENY);
+
+        Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
+                APP_INSTALL_METADATA);
+
+        assertEquals(Rule.EMPTY, matchedRule);
+    }
+
+    @Test
+    public void testMatchRules_openFormulaWithNot() {
+        OpenFormula openSubFormula = new OpenFormula(OpenFormula.Connector.AND, Arrays.asList(
+                new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+                        PACKAGE_NAME_2),
+                new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
+                        AtomicFormula.Operator.EQ,
+                        APP_CERTIFICATE)));
+        OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.NOT,
+                Collections.singletonList(openSubFormula));
+        Rule rule = new Rule(
+                openFormula, Rule.Effect.DENY);
+
+        Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
+                APP_INSTALL_METADATA);
+
+        assertEquals(Rule.EMPTY, matchedRule);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java
index d1fa0f9..048ee70 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java
@@ -19,6 +19,7 @@
 import static com.android.server.testutils.TestUtils.assertExpectException;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 
 import org.junit.Test;
@@ -83,4 +84,20 @@
         assertEquals(String.format("Rule: PACKAGE_NAME EQ %s AND APP_CERTIFICATE EQ %s, DENY",
                 PACKAGE_NAME, APP_CERTIFICATE), toString);
     }
+
+    @Test
+    public void testEquals_trueCase() {
+        Rule rule1 = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT);
+        Rule rule2 = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT);
+
+        assertEquals(rule1, rule2);
+    }
+
+    @Test
+    public void testEquals_falseCase() {
+        Rule rule1 = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT);
+        Rule rule2 = new Rule(APP_CERTIFICATE_ATOMIC_FORMULA, DENY_EFFECT);
+
+        assertNotEquals(rule1, rule2);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index f9ac022..537287d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -35,7 +35,9 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.hardware.authsecret.V1_0.IAuthSecret;
+import android.hardware.face.Face;
 import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.FileUtils;
 import android.os.IProgressListener;
@@ -249,11 +251,32 @@
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
         when(mFingerprintManager.hasEnrolledFingerprints(userId)).thenReturn(true);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Fingerprint fp = (Fingerprint) invocation.getArguments()[0];
+                FingerprintManager.RemovalCallback callback =
+                        (FingerprintManager.RemovalCallback) invocation.getArguments()[2];
+                callback.onRemovalSucceeded(fp, 0);
+                return null;
+            }
+        }).when(mFingerprintManager).remove(any(), eq(userId), any());
+
 
         // Hardware must be detected and templates must be enrolled
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
         when(mFaceManager.isHardwareDetected()).thenReturn(true);
         when(mFaceManager.hasEnrolledTemplates(userId)).thenReturn(true);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Face face = (Face) invocation.getArguments()[0];
+                FaceManager.RemovalCallback callback =
+                        (FaceManager.RemovalCallback) invocation.getArguments()[2];
+                callback.onRemovalSucceeded(face, 0);
+                return null;
+            }
+        }).when(mFaceManager).remove(any(), eq(userId), any());
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index c00d33b..b60111e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -19,10 +19,9 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
 
-import static com.android.internal.widget.LockPatternUtils.stringToPattern;
-
 import static junit.framework.Assert.assertEquals;
 
+import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.never;
@@ -48,6 +47,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockscreenCredential;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -55,6 +56,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+
 /**
  * Test class for {@link LockSettingsShellCommand}.
  *
@@ -87,24 +90,30 @@
     public void testWrongPassword() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
-        when(mLockPatternUtils.checkPassword("1234".getBytes(), mUserId)).thenReturn(false);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPassword("1234"), mUserId, null)).thenReturn(false);
         assertEquals(-1, mCommand.exec(mBinder, in, out, err,
                 new String[] { "set-pin", "--old", "1234" },
                 mShellCallback, mResultReceiver));
-        verify(mLockPatternUtils, never()).saveLockPassword(any(byte[].class), any(byte[].class),
-                anyInt(), anyInt());
+        verify(mLockPatternUtils, never()).setLockCredential(any(), any(),
+                anyInt(), anyBoolean());
     }
 
     @Test
     public void testChangePin() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
-        when(mLockPatternUtils.checkPassword("1234".getBytes(), mUserId)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
+                PASSWORD_QUALITY_NUMERIC);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPin("1234"), mUserId, null)).thenReturn(true);
         assertEquals(0, mCommand.exec(new Binder(), in, out, err,
                 new String[] { "set-pin", "--old", "1234", "4321" },
                 mShellCallback, mResultReceiver));
-        verify(mLockPatternUtils).saveLockPassword("4321".getBytes(), "1234".getBytes(),
-                PASSWORD_QUALITY_NUMERIC, mUserId);
+        verify(mLockPatternUtils).setLockCredential(
+                LockscreenCredential.createPin("4321"),
+                LockscreenCredential.createPin("1234"),
+                mUserId);
     }
 
     @Test
@@ -121,12 +130,17 @@
     public void testChangePassword() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
-        when(mLockPatternUtils.checkPassword("1234".getBytes(), mUserId)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
+                PASSWORD_QUALITY_ALPHABETIC);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPassword("1234"), mUserId, null)).thenReturn(true);
         assertEquals(0,  mCommand.exec(new Binder(), in, out, err,
                 new String[] { "set-password", "--old", "1234", "4321" },
                 mShellCallback, mResultReceiver));
-        verify(mLockPatternUtils).saveLockPassword("4321".getBytes(), "1234".getBytes(),
-                PASSWORD_QUALITY_ALPHABETIC, mUserId);
+        verify(mLockPatternUtils).setLockCredential(
+                LockscreenCredential.createPassword("4321"),
+                LockscreenCredential.createPassword("1234"),
+                mUserId);
     }
 
     @Test
@@ -143,11 +157,15 @@
     public void testChangePattern() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
-        when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPattern(stringToPattern("1234")),
+                mUserId, null)).thenReturn(true);
         assertEquals(0, mCommand.exec(new Binder(), in, out, err,
                 new String[] { "set-pattern", "--old", "1234", "4321" },
                 mShellCallback, mResultReceiver));
-        verify(mLockPatternUtils).saveLockPattern(stringToPattern("4321"), "1234".getBytes(),
+        verify(mLockPatternUtils).setLockCredential(
+                LockscreenCredential.createPattern(stringToPattern("4321")),
+                LockscreenCredential.createPattern(stringToPattern("1234")),
                 mUserId);
     }
 
@@ -165,10 +183,19 @@
     public void testClear() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
-        when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPattern(stringToPattern("1234")),
+                mUserId, null)).thenReturn(true);
         assertEquals(0, mCommand.exec(new Binder(), in, out, err,
                 new String[] { "clear", "--old", "1234" },
                 mShellCallback, mResultReceiver));
-        verify(mLockPatternUtils).clearLock("1234".getBytes(), mUserId);
+        verify(mLockPatternUtils).setLockCredential(
+                LockscreenCredential.createNone(),
+                LockscreenCredential.createPattern(stringToPattern("1234")),
+                mUserId);
+    }
+
+    private List<LockPatternView.Cell> stringToPattern(String str) {
+        return LockPatternUtils.byteArrayToPattern(str.getBytes());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 2a169b7..cb51897 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -434,35 +434,6 @@
         assertEquals(2, PersistentData.TYPE_SP_WEAVER);
     }
 
-    public void testCredentialHash_serializeUnserialize() {
-        byte[] serialized = CredentialHash.create(
-                PAYLOAD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD).toBytes();
-        CredentialHash deserialized = CredentialHash.fromBytes(serialized);
-
-        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, deserialized.type);
-        assertArrayEquals(PAYLOAD, deserialized.hash);
-    }
-
-    public void testCredentialHash_unserialize_versionGatekeeper() {
-        // This test ensures that we can read serialized VERSION_GATEKEEPER CredentialHashes
-        // even if we change the wire format in the future.
-        byte[] serialized = new byte[] {
-                1, /* VERSION_GATEKEEPER */
-                2, /* CREDENTIAL_TYPE_PASSWORD */
-                0, 0, 0, 5, /* hash length */
-                1, 2, -1, -2, 33, /* hash */
-        };
-        CredentialHash deserialized = CredentialHash.fromBytes(serialized);
-
-        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, deserialized.type);
-        assertArrayEquals(PAYLOAD, deserialized.hash);
-
-        // Make sure the constants we use on the wire do not change.
-        assertEquals(-1, LockPatternUtils.CREDENTIAL_TYPE_NONE);
-        assertEquals(1, LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
-        assertEquals(2, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
-    }
-
     private static void assertArrayEquals(byte[] expected, byte[] actual) {
         if (!Arrays.equals(expected, actual)) {
             fail("expected:<" + Arrays.toString(expected) +
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 0776589..42ca42a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -39,6 +39,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
@@ -364,7 +365,7 @@
         // Verify DPM gets notified about new device lock
         flushHandlerTasks();
         final PasswordMetrics metric = PasswordMetrics.computeForCredential(
-                LockPatternUtils.CREDENTIAL_TYPE_PATTERN, pattern);
+                LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(pattern)));
         assertEquals(metric, mService.getUserPasswordMetrics(PRIMARY_USER_ID));
         verify(mDevicePolicyManager).reportPasswordChanged(PRIMARY_USER_ID);
 
@@ -512,7 +513,7 @@
         assertFalse(mService.havePattern(PRIMARY_USER_ID));
     }
 
-    public void testgetHashFactorPrimaryUser() throws RemoteException {
+    public void testGetHashFactorPrimaryUser() throws RemoteException {
         final byte[] password = "password".getBytes();
         mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
                 PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
@@ -527,7 +528,7 @@
         assertArrayEquals(hashFactor, newHashFactor);
     }
 
-    public void testgetHashFactorManagedProfileUnifiedChallenge() throws RemoteException {
+    public void testGetHashFactorManagedProfileUnifiedChallenge() throws RemoteException {
         final byte[] pattern = "1236".getBytes();
         mService.setLockCredential(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
                 null, PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID, false);
@@ -535,7 +536,7 @@
         assertNotNull(mService.getHashFactor(null, MANAGED_PROFILE_USER_ID));
     }
 
-    public void testgetHashFactorManagedProfileSeparateChallenge() throws RemoteException {
+    public void testGetHashFactorManagedProfileSeparateChallenge() throws RemoteException {
         final byte[] primaryPassword = "primary".getBytes();
         final byte[] profilePassword = "profile".getBytes();
         mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index ba12b73..8a48904 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -113,6 +113,7 @@
 import android.net.NetworkTemplate;
 import android.net.StringNetworkSpecifier;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.INetworkManagementService;
 import android.os.PersistableBundle;
 import android.os.PowerManagerInternal;
@@ -1117,7 +1118,7 @@
         // Define simple data plan
         final SubscriptionPlan plan = buildMonthlyDataPlan(
                 ZonedDateTime.parse("2015-11-01T00:00:00.00Z"), DataUnit.MEGABYTES.toBytes(1800));
-        mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
+        setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
                 mServiceContext.getOpPackageName());
 
         // We're 20% through the month (6 days)
@@ -1241,7 +1242,7 @@
         // Define simple data plan which gives us effectively 60MB/day
         final SubscriptionPlan plan = buildMonthlyDataPlan(
                 ZonedDateTime.parse("2015-11-01T00:00:00.00Z"), DataUnit.MEGABYTES.toBytes(1800));
-        mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
+        setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
                 mServiceContext.getOpPackageName());
 
         // We're 20% through the month (6 days)
@@ -1457,6 +1458,8 @@
         when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[0]);
         when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{FAKE_SUB_ID});
         when(mTelephonyManager.getSubscriberId(FAKE_SUB_ID)).thenReturn(FAKE_SUBSCRIBER_ID);
+        when(mTelephonyManager.createForSubscriptionId(FAKE_SUB_ID))
+                .thenReturn(mock(TelephonyManager.class));
         PersistableBundle bundle = CarrierConfigManager.getDefaultConfig();
         when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(bundle);
         setNetworkPolicies(buildDefaultFakeMobilePolicy());
@@ -1468,6 +1471,8 @@
         when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[0]);
         when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{FAKE_SUB_ID});
         when(mTelephonyManager.getSubscriberId(FAKE_SUB_ID)).thenReturn(FAKE_SUBSCRIBER_ID);
+        when(mTelephonyManager.createForSubscriptionId(FAKE_SUB_ID))
+                .thenReturn(mock(TelephonyManager.class));
         when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(null);
         setNetworkPolicies(buildDefaultFakeMobilePolicy());
         // smoke test to make sure no errors are raised
@@ -1653,7 +1658,7 @@
             final SubscriptionPlan plan = buildMonthlyDataPlan(
                     ZonedDateTime.parse("2015-11-01T00:00:00.00Z"),
                     DataUnit.MEGABYTES.toBytes(1800));
-            mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
+            setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
                     mServiceContext.getOpPackageName());
 
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
@@ -1674,7 +1679,7 @@
             final SubscriptionPlan plan = buildMonthlyDataPlan(
                     ZonedDateTime.parse("2015-11-01T00:00:00.00Z"),
                     DataUnit.MEGABYTES.toBytes(100));
-            mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
+            setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
                     mServiceContext.getOpPackageName());
 
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
@@ -1690,7 +1695,7 @@
         {
             final SubscriptionPlan plan = buildMonthlyDataPlan(
                     ZonedDateTime.parse("2015-11-01T00:00:00.00Z"), BYTES_UNLIMITED);
-            mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
+            setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
                     mServiceContext.getOpPackageName());
 
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
@@ -1707,7 +1712,7 @@
         {
             final SubscriptionPlan plan = buildMonthlyDataPlan(
                     ZonedDateTime.parse("2015-11-01T00:00:00.00Z"), BYTES_UNLIMITED);
-            mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
+            setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
                     mServiceContext.getOpPackageName());
 
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
@@ -1923,6 +1928,8 @@
         when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(
                 new int[] { TEST_SUB_ID });
         when(mTelephonyManager.getSubscriberId(TEST_SUB_ID)).thenReturn(TEST_IMSI);
+        when(mTelephonyManager.createForSubscriptionId(TEST_SUB_ID))
+                .thenReturn(mock(TelephonyManager.class));
         doNothing().when(mTelephonyManager).setPolicyDataEnabled(anyBoolean(), anyInt());
         expectNetworkState(false /* roaming */);
     }
@@ -2049,6 +2056,23 @@
 
     private FutureIntent mRestrictBackgroundChanged;
 
+    private void postMsgAndWaitForCompletion() throws InterruptedException {
+        final Handler handler = mService.getHandlerForTesting();
+        final CountDownLatch latch = new CountDownLatch(1);
+        mService.getHandlerForTesting().post(latch::countDown);
+        if (!latch.await(5, TimeUnit.SECONDS)) {
+            fail("Timed out waiting for the test msg to be handled");
+        }
+    }
+
+    private void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage)
+            throws InterruptedException {
+        mService.setSubscriptionPlans(subId, plans, callingPackage);
+        // setSubscriptionPlans() triggers async events, wait for those to be completed before
+        // moving forward as they could interfere with the tests later.
+        postMsgAndWaitForCompletion();
+    }
+
     private void setRestrictBackground(boolean flag) throws Exception {
         mService.setRestrictBackground(flag);
         // Sanity check.
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index 43bcd4f..7c2a097 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -167,6 +167,7 @@
                 /* userId */  456,
                 /* installerPackageName */ "testInstaller",
                 /* installerUid */ -1,
+                InstallSource.create("testInstaller"),
                 /* sessionParams */ params,
                 /* createdMillis */ 0L,
                 /* stageDir */ mTmpDir,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 8c3373f..3ae5674 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -22,6 +22,7 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNull;
@@ -105,6 +106,7 @@
     private int mUid = 1000;
     private int mPid = 2000;
     private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+    private NotificationChannel mChannel;
 
     private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1);
     private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0);
@@ -158,6 +160,8 @@
         mService.mScreenOn = false;
         mService.mInCallStateOffHook = false;
         mService.mNotificationPulseEnabled = true;
+
+        mChannel = new NotificationChannel("test", "test", IMPORTANCE_HIGH);
     }
 
     //
@@ -174,13 +178,18 @@
                 true /* noisy */, false /* buzzy*/, false /* lights */);
     }
 
+    private NotificationRecord getBeepyOtherNotification() {
+        return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
+                true /* noisy */, false /* buzzy*/, false /* lights */);
+    }
+
     private NotificationRecord getBeepyOnceNotification() {
         return getNotificationRecord(mId, false /* insistent */, true /* once */,
                 true /* noisy */, false /* buzzy*/, false /* lights */);
     }
 
     private NotificationRecord getQuietNotification() {
-        return getNotificationRecord(mId, false /* insistent */, false /* once */,
+        return getNotificationRecord(mId, false /* insistent */, true /* once */,
                 false /* noisy */, false /* buzzy*/, false /* lights */);
     }
 
@@ -214,6 +223,11 @@
                 false /* noisy */, true /* buzzy*/, false /* lights */);
     }
 
+    private NotificationRecord getBuzzyOtherNotification() {
+        return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
+                false /* noisy */, true /* buzzy*/, false /* lights */);
+    }
+
     private NotificationRecord getBuzzyOnceNotification() {
         return getNotificationRecord(mId, false /* insistent */, true /* once */,
                 false /* noisy */, true /* buzzy*/, false /* lights */);
@@ -239,22 +253,34 @@
                 false /* noisy */, false /* buzzy*/, true /* lights */);
     }
 
-    private NotificationRecord getCallRecord(int id, boolean insistent) {
-        return getNotificationRecord(id, false, false /* once */, true /* noisy */,
-                false /* buzzy */, false /* lights */, false /* default vib */,
-                false /* default sound */, false /* default lights */, "",
-                Notification.GROUP_ALERT_ALL, false);
+    private NotificationRecord getCallRecord(int id, NotificationChannel channel, boolean looping) {
+        final Builder builder = new Builder(getContext())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setPriority(Notification.PRIORITY_HIGH);
+        Notification n = builder.build();
+        if (looping) {
+            n.flags |= Notification.FLAG_INSISTENT;
+        }
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
+                mPid, n, mUser, null, System.currentTimeMillis());
+        NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+        mService.addNotification(r);
+
+        return r;
     }
 
     private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
             boolean noisy, boolean buzzy, boolean lights) {
-        return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true,
-                null, Notification.GROUP_ALERT_ALL, false);
+        return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, buzzy, noisy,
+                lights, null, Notification.GROUP_ALERT_ALL, false);
     }
 
-    private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent, boolean once,
+    private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent,
+            boolean once,
             boolean noisy, boolean buzzy, boolean lights) {
-        return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true,
+        return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true,
+                true,
                 null, Notification.GROUP_ALERT_ALL, true);
     }
 
@@ -265,16 +291,16 @@
 
     private NotificationRecord getLightsNotificationRecord(String groupKey,
             int groupAlertBehavior) {
-        return getNotificationRecord(mId, false, false, false, false, true /*lights*/, true, true,
-                true, groupKey, groupAlertBehavior, false);
+        return getNotificationRecord(mId, false, false, false, false, true /*lights*/, true,
+                true, true, groupKey, groupAlertBehavior, false);
     }
 
-    private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
+    private NotificationRecord getNotificationRecord(int id,
+            boolean insistent, boolean once,
             boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
             boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
             boolean isLeanback) {
-        NotificationChannel channel =
-                new NotificationChannel("test", "test", IMPORTANCE_HIGH);
+
         final Builder builder = new Builder(getContext())
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -285,31 +311,37 @@
         if (noisy) {
             if (defaultSound) {
                 defaults |= Notification.DEFAULT_SOUND;
-                channel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
+                mChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
                         Notification.AUDIO_ATTRIBUTES_DEFAULT);
             } else {
                 builder.setSound(CUSTOM_SOUND);
-                channel.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES);
+                mChannel.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES);
             }
         } else {
-            channel.setSound(null, null);
+            mChannel.setSound(null, null);
         }
         if (buzzy) {
             if (defaultVibration) {
                 defaults |= Notification.DEFAULT_VIBRATE;
             } else {
                 builder.setVibrate(CUSTOM_VIBRATION);
-                channel.setVibrationPattern(CUSTOM_VIBRATION);
+                mChannel.setVibrationPattern(CUSTOM_VIBRATION);
             }
-            channel.enableVibration(true);
+            mChannel.enableVibration(true);
+        } else {
+            mChannel.setVibrationPattern(null);
+            mChannel.enableVibration(false);
         }
+
         if (lights) {
             if (defaultLights) {
                 defaults |= Notification.DEFAULT_LIGHTS;
             } else {
                 builder.setLights(CUSTOM_LIGHT_COLOR, CUSTOM_LIGHT_ON, CUSTOM_LIGHT_OFF);
             }
-            channel.enableLights(true);
+            mChannel.enableLights(true);
+        } else {
+            mChannel.enableLights(false);
         }
         builder.setDefaults(defaults);
 
@@ -329,7 +361,7 @@
 
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
                 mPid, n, mUser, null, System.currentTimeMillis());
-        NotificationRecord r = new NotificationRecord(context, sbn, channel);
+        NotificationRecord r = new NotificationRecord(context, sbn, mChannel);
         mService.addNotification(r);
         return r;
     }
@@ -339,18 +371,19 @@
     //
 
     private void verifyNeverBeep() throws RemoteException {
-        verify(mRingtonePlayer, never()).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
-                anyBoolean(), (AudioAttributes) anyObject());
+        verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any());
     }
 
-    private void verifyBeep() throws RemoteException {
-        verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
-                eq(true), (AudioAttributes) anyObject());
+    private void verifyBeepUnlooped() throws RemoteException  {
+        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any());
     }
 
-    private void verifyBeepLooped() throws RemoteException {
-        verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
-                eq(false), (AudioAttributes) anyObject());
+    private void verifyBeepLooped() throws RemoteException  {
+        verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any());
+    }
+
+    private void verifyBeep(int times)  throws RemoteException  {
+        verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any());
     }
 
     private void verifyNeverStopAudio() throws RemoteException {
@@ -362,24 +395,31 @@
     }
 
     private void verifyNeverVibrate() {
-        verify(mVibrator, never()).vibrate(anyInt(), anyString(), (VibrationEffect) anyObject(),
-                anyString(), (AudioAttributes) anyObject());
+        verify(mVibrator, never()).vibrate(anyInt(), anyString(), any(), anyString(), any());
     }
 
     private void verifyVibrate() {
         verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
-                anyString(), (AudioAttributes) anyObject());
+                anyString(), any());
+    }
+
+    private void verifyVibrate(int times) {
+        verify(mVibrator, times(times)).vibrate(anyInt(), anyString(), any(), anyString(), any());
     }
 
     private void verifyVibrateLooped() {
         verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher),
-                anyString(), (AudioAttributes) anyObject());
+                anyString(), any());
     }
 
     private void verifyDelayedVibrateLooped() {
         verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
-                argThat(mVibrateLoopMatcher), anyString(),
-                (AudioAttributes) anyObject());
+                argThat(mVibrateLoopMatcher), anyString(), any());
+    }
+
+    private void verifyDelayedVibrate() {
+        verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
+                argThat(mVibrateOnceMatcher), anyString(), any());
     }
 
     private void verifyStopVibrate() {
@@ -398,11 +438,6 @@
         verify(mLight, times(1)).setFlashing(anyInt(), anyInt(), anyInt(), anyInt());
     }
 
-    private void verifyCustomLights() {
-        verify(mLight, times(1)).setFlashing(
-                eq(CUSTOM_LIGHT_COLOR), anyInt(), eq(CUSTOM_LIGHT_ON), eq(CUSTOM_LIGHT_OFF));
-    }
-
     //
     // Tests
     //
@@ -425,7 +460,7 @@
 
         mService.buzzBeepBlinkLocked(r);
 
-        verifyBeepLooped();
+        verifyBeepUnlooped();
         verifyNeverVibrate();
         verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
         assertTrue(r.isInterruptive());
@@ -438,7 +473,7 @@
 
         mService.buzzBeepBlinkLocked(r);
 
-        verifyBeep();
+        verifyBeepLooped();
         assertTrue(r.isInterruptive());
         assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
@@ -492,7 +527,7 @@
 
         mService.buzzBeepBlinkLocked(r);
 
-        verifyBeepLooped();
+        verifyBeepUnlooped();
         assertTrue(r.isInterruptive());
     }
 
@@ -533,7 +568,7 @@
         // update should beep
         r.isUpdate = true;
         mService.buzzBeepBlinkLocked(r);
-        verifyBeepLooped();
+        verifyBeepUnlooped();
         verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
         assertTrue(r.isInterruptive());
         assertNotEquals(-1, r.getLastAudiblyAlertedMs());
@@ -723,7 +758,7 @@
         mService.buzzBeepBlinkLocked(r);
 
         verifyNeverVibrate();
-        verifyBeepLooped();
+        verifyBeepUnlooped();
         assertTrue(r.isInterruptive());
         assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
@@ -821,7 +856,7 @@
 
         mService.buzzBeepBlinkLocked(summary);
 
-        verifyBeepLooped();
+        verifyBeepUnlooped();
         // summaries are never interruptive for notification counts
         assertFalse(summary.isInterruptive());
         assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
@@ -833,7 +868,7 @@
 
         mService.buzzBeepBlinkLocked(nonGroup);
 
-        verifyBeepLooped();
+        verifyBeepUnlooped();
         assertTrue(nonGroup.isInterruptive());
         assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs());
     }
@@ -856,7 +891,7 @@
 
         mService.buzzBeepBlinkLocked(child);
 
-        verifyBeepLooped();
+        verifyBeepUnlooped();
         assertTrue(child.isInterruptive());
         assertNotEquals(-1, child.getLastAudiblyAlertedMs());
     }
@@ -867,7 +902,7 @@
 
         mService.buzzBeepBlinkLocked(nonGroup);
 
-        verifyBeepLooped();
+        verifyBeepUnlooped();
         assertTrue(nonGroup.isInterruptive());
         assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs());
     }
@@ -878,7 +913,7 @@
 
         mService.buzzBeepBlinkLocked(group);
 
-        verifyBeepLooped();
+        verifyBeepUnlooped();
         assertTrue(group.isInterruptive());
         assertNotEquals(-1, group.getLastAudiblyAlertedMs());
     }
@@ -1293,7 +1328,11 @@
 
     @Test
     public void testListenerHintCall() throws Exception {
-        NotificationRecord r = getCallRecord(1, true);
+        NotificationChannel ringtoneChannel =
+                new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+        ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+                new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+        NotificationRecord r = getCallRecord(1, ringtoneChannel, true);
 
         mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS);
 
@@ -1310,7 +1349,7 @@
 
         mService.buzzBeepBlinkLocked(r);
 
-        verifyBeepLooped();
+        verifyBeepUnlooped();
     }
 
     @Test
@@ -1326,7 +1365,11 @@
 
     @Test
     public void testListenerHintBoth() throws Exception {
-        NotificationRecord r = getCallRecord(1, true);
+        NotificationChannel ringtoneChannel =
+                new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+        ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+                new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+        NotificationRecord r = getCallRecord(1, ringtoneChannel, true);
         NotificationRecord s = getBeepyNotification();
 
         mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS
@@ -1340,7 +1383,11 @@
 
     @Test
     public void testListenerHintNotification_callSound() throws Exception {
-        NotificationRecord r = getCallRecord(1, true);
+        NotificationChannel ringtoneChannel =
+                new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+        ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+                new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+        NotificationRecord r = getCallRecord(1, ringtoneChannel, true);
 
         mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS);
 
@@ -1349,6 +1396,167 @@
         verifyBeepLooped();
     }
 
+    @Test
+    public void testCannotInterruptRingtoneInsistentBeep() throws Exception {
+        NotificationChannel ringtoneChannel =
+                new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+        ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+                new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+        NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+        mService.addNotification(ringtoneNotification);
+
+        mService.buzzBeepBlinkLocked(ringtoneNotification);
+        verifyBeepLooped();
+
+        NotificationRecord interrupter = getBeepyOtherNotification();
+        assertTrue(mService.shouldMuteNotificationLocked(interrupter));
+        mService.buzzBeepBlinkLocked(interrupter);
+
+        verifyBeep(1);
+
+        assertFalse(interrupter.isInterruptive());
+        assertEquals(-1, interrupter.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testCannotInterruptRingtoneInsistentBuzz() {
+        NotificationChannel ringtoneChannel =
+                new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+        ringtoneChannel.setSound(Uri.EMPTY,
+                new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+        ringtoneChannel.enableVibration(true);
+        NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+        assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
+
+        mService.buzzBeepBlinkLocked(ringtoneNotification);
+        verifyVibrateLooped();
+
+        NotificationRecord interrupter = getBuzzyOtherNotification();
+        assertTrue(mService.shouldMuteNotificationLocked(interrupter));
+        mService.buzzBeepBlinkLocked(interrupter);
+
+        verifyVibrate(1);
+
+        assertFalse(interrupter.isInterruptive());
+        assertEquals(-1, interrupter.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testCanInterruptRingtoneNonInsistentBeep() throws Exception {
+        NotificationChannel ringtoneChannel =
+                new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+        ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+                new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+        NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, false);
+
+        mService.buzzBeepBlinkLocked(ringtoneNotification);
+        verifyBeepUnlooped();
+
+        NotificationRecord interrupter = getBeepyOtherNotification();
+        mService.buzzBeepBlinkLocked(interrupter);
+
+        verifyBeep(2);
+
+        assertTrue(interrupter.isInterruptive());
+    }
+
+    @Test
+    public void testCanInterruptRingtoneNonInsistentBuzz() {
+        NotificationChannel ringtoneChannel =
+                new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+        ringtoneChannel.setSound(null,
+                new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+        ringtoneChannel.enableVibration(true);
+        NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, false);
+
+        mService.buzzBeepBlinkLocked(ringtoneNotification);
+        verifyVibrate();
+
+        NotificationRecord interrupter = getBuzzyOtherNotification();
+        mService.buzzBeepBlinkLocked(interrupter);
+
+        verifyVibrate(2);
+
+        assertTrue(interrupter.isInterruptive());
+    }
+
+    @Test
+    public void testRingtoneInsistentBeep_doesNotBlockFutureSoundsOnceStopped() throws Exception {
+        NotificationChannel ringtoneChannel =
+                new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+        ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+                new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+        NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+
+        mService.buzzBeepBlinkLocked(ringtoneNotification);
+        verifyBeepLooped();
+
+        mService.clearSoundLocked();
+
+        NotificationRecord interrupter = getBeepyOtherNotification();
+        mService.buzzBeepBlinkLocked(interrupter);
+
+        verifyBeep(2);
+
+        assertTrue(interrupter.isInterruptive());
+    }
+
+    @Test
+    public void testRingtoneInsistentBuzz_doesNotBlockFutureSoundsOnceStopped() {
+        NotificationChannel ringtoneChannel =
+                new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+        ringtoneChannel.setSound(null,
+                new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+        ringtoneChannel.enableVibration(true);
+        NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+
+        mService.buzzBeepBlinkLocked(ringtoneNotification);
+        verifyVibrateLooped();
+
+        mService.clearVibrateLocked();
+
+        NotificationRecord interrupter = getBuzzyOtherNotification();
+        mService.buzzBeepBlinkLocked(interrupter);
+
+        verifyVibrate(2);
+
+        assertTrue(interrupter.isInterruptive());
+    }
+
+    @Test
+    public void testCanInterruptNonRingtoneInsistentBeep() throws Exception {
+        NotificationChannel fakeRingtoneChannel =
+                new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+        NotificationRecord ringtoneNotification = getCallRecord(1, fakeRingtoneChannel, true);
+
+        mService.buzzBeepBlinkLocked(ringtoneNotification);
+        verifyBeepLooped();
+
+        NotificationRecord interrupter = getBeepyOtherNotification();
+        mService.buzzBeepBlinkLocked(interrupter);
+
+        verifyBeep(2);
+
+        assertTrue(interrupter.isInterruptive());
+    }
+
+    @Test
+    public void testCanInterruptNonRingtoneInsistentBuzz() throws Exception {
+        NotificationChannel fakeRingtoneChannel =
+                new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+        fakeRingtoneChannel.enableVibration(true);
+        NotificationRecord ringtoneNotification = getCallRecord(1, fakeRingtoneChannel, true);
+
+        mService.buzzBeepBlinkLocked(ringtoneNotification);
+
+        NotificationRecord interrupter = getBuzzyOtherNotification();
+        mService.buzzBeepBlinkLocked(interrupter);
+
+        verifyVibrate(2);
+
+        assertTrue(interrupter.isInterruptive());
+    }
+
     static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
         private final int mRepeatIndex;
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 4ea2fc0..cd0f4f1 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -537,6 +537,18 @@
         return new NotificationRecord(mContext, sbn, channel);
     }
 
+    private NotificationRecord generateNotificationRecord(NotificationChannel channel, int userId) {
+        if (channel == null) {
+            channel = mTestNotificationChannel;
+        }
+        Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+                nb.build(), new UserHandle(userId), null, 0);
+        return new NotificationRecord(mContext, sbn, channel);
+    }
+
     private Map<String, Answer> getSignalExtractorSideEffects() {
         Map<String, Answer> answers = new ArrayMap<>();
 
@@ -5451,6 +5463,112 @@
     }
 
     @Test
+    public void testGrantInlineReplyUriPermission_recordExists() throws Exception {
+        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, 0);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // A notification exists for the given record
+        StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsBefore.length);
+
+        reset(mPackageManager);
+
+        Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+
+        mService.mNotificationDelegate.grantInlineReplyUriPermission(
+                nr.getKey(), uri, nr.sbn.getUid());
+
+        // Grant permission called for the UID of SystemUI under the target user ID
+        verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
+                eq(nr.sbn.getUid()), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
+                eq(nr.sbn.getUserId()));
+    }
+
+    @Test
+    public void testGrantInlineReplyUriPermission_userAll() throws Exception {
+        // generate a NotificationRecord for USER_ALL to make sure it's converted into USER_SYSTEM
+        NotificationRecord nr =
+                generateNotificationRecord(mTestNotificationChannel, UserHandle.USER_ALL);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // A notification exists for the given record
+        StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsBefore.length);
+
+        reset(mPackageManager);
+
+        Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+
+        mService.mNotificationDelegate.grantInlineReplyUriPermission(
+                nr.getKey(), uri, nr.sbn.getUid());
+
+        // Target user for the grant is USER_ALL instead of USER_SYSTEM
+        verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
+                eq(nr.sbn.getUid()), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
+                eq(UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void testGrantInlineReplyUriPermission_acrossUsers() throws Exception {
+        // generate a NotificationRecord for USER_ALL to make sure it's converted into USER_SYSTEM
+        int otherUserId = 11;
+        NotificationRecord nr =
+                generateNotificationRecord(mTestNotificationChannel, otherUserId);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // A notification exists for the given record
+        StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsBefore.length);
+
+        reset(mPackageManager);
+
+        Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+
+        int uid = 0; // sysui on primary user
+        int otherUserUid = (otherUserId * 100000) + 1; // SystemUI as a different user
+        String sysuiPackage = "sysui";
+        final String[] sysuiPackages = new String[] { sysuiPackage };
+        when(mPackageManager.getPackagesForUid(uid)).thenReturn(sysuiPackages);
+
+        // Make sure to mock call for USER_SYSTEM and not USER_ALL, since it's been replaced by the
+        // time this is called
+        when(mPackageManager.getPackageUid(sysuiPackage, 0, otherUserId))
+                .thenReturn(otherUserUid);
+
+        mService.mNotificationDelegate.grantInlineReplyUriPermission(nr.getKey(), uri, uid);
+
+        // Target user for the grant is USER_ALL instead of USER_SYSTEM
+        verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
+                eq(otherUserUid), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
+                eq(otherUserId));
+    }
+
+    @Test
+    public void testGrantInlineReplyUriPermission_noRecordExists() throws Exception {
+        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+        waitForIdle();
+
+        // No notifications exist for the given record
+        StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+        assertEquals(0, notifsBefore.length);
+
+        Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+        int uid = 0; // sysui on primary user
+
+        mService.mNotificationDelegate.grantInlineReplyUriPermission(nr.getKey(), uri, uid);
+
+        // Grant permission not called if no record exists for the given key
+        verify(mUgm, times(0)).grantUriPermissionFromOwner(any(), anyInt(), any(),
+                eq(uri), anyInt(), anyInt(), anyInt());
+    }
+
+    @Test
     public void testNotificationBubbles_disabled_lowRamDevice() throws Exception {
         // Bubbles are allowed!
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index aaaa7a5..2836e69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -28,6 +28,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 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.Mockito.timeout;
@@ -128,7 +129,7 @@
 
         mActivityMetricsLogger.notifyActivityLaunching(intent);
 
-        verifyAsync(mLaunchObserver).onIntentStarted(eq(intent));
+        verifyAsync(mLaunchObserver).onIntentStarted(eq(intent), anyLong());
         verifyNoMoreInteractions(mLaunchObserver);
     }
 
@@ -163,12 +164,12 @@
        testOnActivityLaunched();
 
        mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
-               SystemClock.uptimeMillis());
+               SystemClock.elapsedRealtimeNanos());
 
        mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecord.getWindowingMode(),
-               SystemClock.uptimeMillis());
+               SystemClock.elapsedRealtimeNanos());
 
-       verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecord));
+       verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecord), anyLong());
        verifyNoMoreInteractions(mLaunchObserver);
     }
 
@@ -186,6 +187,16 @@
     }
 
     @Test
+    public void testOnReportFullyDrawn() throws Exception {
+        testOnActivityLaunched();
+
+        mActivityMetricsLogger.logAppTransitionReportedDrawn(mActivityRecord, false);
+
+        verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mActivityRecord), anyLong());
+        verifyNoMoreInteractions(mLaunchObserver);
+    }
+
+    @Test
     public void testOnActivityLaunchedTrampoline() throws Exception {
         testOnIntentStarted();
 
@@ -206,12 +217,13 @@
        testOnActivityLaunchedTrampoline();
 
        mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
-               SystemClock.uptimeMillis());
+               SystemClock.elapsedRealtimeNanos());
 
        mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecordTrampoline.getWindowingMode(),
-               SystemClock.uptimeMillis());
+               SystemClock.elapsedRealtimeNanos());
 
-       verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecordTrampoline));
+       verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecordTrampoline),
+                                                             anyLong());
        verifyNoMoreInteractions(mLaunchObserver);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index c2a05c24..2835c1f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -891,7 +891,7 @@
         activity.app = null;
         overlayActivity.app = null;
 
-        assertEquals(2, mTask.mActivities.size());
+        assertEquals(2, mTask.getChildCount());
 
         mStack.finishDisabledPackageActivitiesLocked(activity.packageName,
                 null  /* filterByClasses */, true /* doit */, true /* evenPersistent */,
@@ -900,7 +900,7 @@
         // Although the overlay activity is in another package, the non-overlay activities are
         // removed from the task. Since the overlay activity should be removed as well, the task
         // should be empty.
-        assertThat(mTask.mActivities).isEmpty();
+        assertFalse(mTask.hasChild());
         assertThat(mStack.getAllTasks()).isEmpty();
     }
 
@@ -918,11 +918,11 @@
         // second activity will be immediately removed as it has no state.
         secondActivity.setSavedState(null /* savedState */);
 
-        assertEquals(2, mTask.mActivities.size());
+        assertEquals(2, mTask.getChildCount());
 
         mStack.handleAppDiedLocked(secondActivity.app);
 
-        assertThat(mTask.mActivities).isEmpty();
+        assertFalse(mTask.hasChild());
         assertThat(mStack.getAllTasks()).isEmpty();
     }
 
@@ -936,7 +936,7 @@
 
         mStack.handleAppDiedLocked(activity.app);
 
-        assertEquals(1, mTask.mActivities.size());
+        assertEquals(1, mTask.getChildCount());
         assertEquals(1, mStack.getAllTasks().size());
     }
 
@@ -950,7 +950,7 @@
 
         mStack.handleAppDiedLocked(activity.app);
 
-        assertThat(mTask.mActivities).isEmpty();
+        assertFalse(mTask.hasChild());
         assertThat(mStack.getAllTasks()).isEmpty();
     }
 
@@ -964,7 +964,7 @@
 
         mStack.handleAppDiedLocked(activity.app);
 
-        assertEquals(1, mTask.mActivities.size());
+        assertEquals(1, mTask.getChildCount());
         assertEquals(1, mStack.getAllTasks().size());
     }
 
@@ -978,7 +978,7 @@
 
         mStack.handleAppDiedLocked(activity.app);
 
-        assertThat(mTask.mActivities).isEmpty();
+        assertFalse(mTask.hasChild());
         assertThat(mStack.getAllTasks()).isEmpty();
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 77fbdcf..d43fe63 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -215,23 +215,6 @@
             return this;
         }
 
-        static Pair<Intent, ActivityInfo> createIntentAndActivityInfo() {
-            // TODO: Look into consolidating with dup. code in build() method below.
-            final int id = sCurrentActivityId++;
-            final ComponentName component = ComponentName.createRelative(
-                    DEFAULT_COMPONENT_PACKAGE_NAME, DEFAULT_COMPONENT_CLASS_NAME + id);
-
-            final Intent intent = new Intent();
-            intent.setComponent(component);
-
-            final ActivityInfo aInfo = new ActivityInfo();
-            aInfo.applicationInfo = new ApplicationInfo();
-            aInfo.applicationInfo.packageName = component.getPackageName();
-            aInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
-            aInfo.packageName = component.getPackageName();
-            return new Pair<>(intent, aInfo);
-        }
-
         ActivityRecord build() {
             if (mComponent == null) {
                 final int id = sCurrentActivityId++;
@@ -249,6 +232,7 @@
             intent.setComponent(mComponent);
             final ActivityInfo aInfo = new ActivityInfo();
             aInfo.applicationInfo = new ApplicationInfo();
+            aInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
             aInfo.applicationInfo.packageName = mComponent.getPackageName();
             aInfo.applicationInfo.uid = mUid;
             aInfo.packageName = mComponent.getPackageName();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 650a911..b174251 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -57,7 +57,7 @@
 
     private TaskStack mStack;
     private Task mTask;
-    private AppWindowToken mToken;
+    private ActivityRecord mToken;
 
     public void setUpOnDisplay(DisplayContent dc) {
         mStack = createTaskStackOnDisplay(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, dc);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 605d520..14939cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -101,10 +101,10 @@
     @Test
     @FlakyTest(bugId = 131005232)
     public void testTransitWithinTask() {
-        final AppWindowToken opening = createAppWindowToken(mDisplayContent,
+        final ActivityRecord opening = createAppWindowToken(mDisplayContent,
                 WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
         opening.setOccludesParent(false);
-        final AppWindowToken closing = createAppWindowToken(mDisplayContent,
+        final ActivityRecord closing = createAppWindowToken(mDisplayContent,
                 WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
         closing.setOccludesParent(false);
         final Task task = opening.getTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 72d9bd0..9d53676 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -159,7 +159,7 @@
 
         final TaskStack stack1 = createTaskStackOnDisplay(dc1);
         final Task task1 = createTaskInStack(stack1, 0 /* userId */);
-        final AppWindowToken token1 =
+        final ActivityRecord token1 =
                 WindowTestUtils.createTestAppWindowToken(dc1);
         task1.addChild(token1, 0);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 2661735..b4c978f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -77,7 +77,7 @@
 
     TaskStack mStack;
     Task mTask;
-    AppWindowToken mToken;
+    ActivityRecord mToken;
 
     private final String mPackageName = getInstrumentation().getTargetContext().getPackageName();
 
@@ -410,7 +410,7 @@
     }
 
     private AppWindowToken createTestAppWindowTokenForGivenTask(Task task) {
-        final AppWindowToken appToken =
+        final ActivityRecord appToken =
                 WindowTestUtils.createTestAppWindowToken(mDisplayContent);
         task.addChild(appToken, 0);
         waitUntilHandlersIdle();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 2ba3cbd..f12c349 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -241,7 +241,7 @@
         assertEquals(dc, stack.getDisplayContent());
 
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(dc);
+        final ActivityRecord token = WindowTestUtils.createTestAppWindowToken(dc);
         task.addChild(token, 0);
         assertEquals(dc, task.getDisplayContent());
         assertEquals(dc, token.getDisplayContent());
@@ -313,7 +313,7 @@
         // Add stack with activity.
         final TaskStack stack0 = createTaskStackOnDisplay(dc0);
         final Task task0 = createTaskInStack(stack0, 0 /* userId */);
-        final AppWindowToken token =
+        final ActivityRecord token =
                 WindowTestUtils.createTestAppWindowToken(dc0);
         task0.addChild(token, 0);
         dc0.configureDisplayPolicy();
@@ -321,7 +321,7 @@
 
         final TaskStack stack1 = createTaskStackOnDisplay(dc1);
         final Task task1 = createTaskInStack(stack1, 0 /* userId */);
-        final AppWindowToken token1 =
+        final ActivityRecord token1 =
                 WindowTestUtils.createTestAppWindowToken(dc0);
         task1.addChild(token1, 0);
         dc1.configureDisplayPolicy();
@@ -682,16 +682,15 @@
         // is appWin & null on the other display.
         mDisplayContent.setInputMethodWindowLocked(mImeWindow);
         newDisplay.setInputMethodWindowLocked(null);
-        assertTrue("appWin should be IME target window",
-                appWin.equals(mDisplayContent.mInputMethodTarget));
+        assertEquals("appWin should be IME target window",
+                appWin, mDisplayContent.mInputMethodTarget);
         assertNull("newDisplay Ime target: ", newDisplay.mInputMethodTarget);
 
         // Switch input method window on new display & make sure the input method target also
         // switched as expected.
         newDisplay.setInputMethodWindowLocked(mImeWindow);
         mDisplayContent.setInputMethodWindowLocked(null);
-        assertTrue("appWin1 should be IME target window",
-                appWin1.equals(newDisplay.mInputMethodTarget));
+        assertEquals("appWin1 should be IME target window", appWin1, newDisplay.mInputMethodTarget);
         assertNull("default display Ime target: ", mDisplayContent.mInputMethodTarget);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 304df22..452e06f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -96,7 +96,7 @@
      * Creates a window state which can be used as a drop target.
      */
     private WindowState createDropTargetWindow(String name, int ownerId) {
-        final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(
+        final ActivityRecord token = WindowTestUtils.createTestAppWindowToken(
                 mDisplayContent);
         final TaskStack stack = createTaskStackOnDisplay(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java
index 92ddb35..eef680b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java
@@ -54,7 +54,7 @@
         // Stack should contain visible app window to be considered visible.
         final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */);
         assertFalse(mPinnedStack.isVisible());
-        final AppWindowToken pinnedApp =
+        final ActivityRecord pinnedApp =
                 WindowTestUtils.createTestAppWindowToken(mDisplayContent);
         pinnedTask.addChild(pinnedApp, 0 /* addPos */);
         assertTrue(mPinnedStack.isVisible());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
index 2eb6ea4..d045073 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
@@ -68,13 +68,13 @@
     public void testClosingAppDifferentStackOrientation() {
         final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack, 0 /* userId */);
-        AppWindowToken appWindowToken1 =
+        ActivityRecord appWindowToken1 =
                 WindowTestUtils.createTestAppWindowToken(mDisplayContent);
         task1.addChild(appWindowToken1, 0);
         appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
         final Task task2 = createTaskInStack(stack, 1 /* userId */);
-        AppWindowToken appWindowToken2 =
+        ActivityRecord appWindowToken2 =
                 WindowTestUtils.createTestAppWindowToken(mDisplayContent);
         task2.addChild(appWindowToken2, 0);
         appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
@@ -88,13 +88,13 @@
     public void testMoveTaskToBackDifferentStackOrientation() {
         final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task1 = createTaskInStack(stack, 0 /* userId */);
-        AppWindowToken appWindowToken1 =
+        ActivityRecord appWindowToken1 =
                 WindowTestUtils.createTestAppWindowToken(mDisplayContent);
         task1.addChild(appWindowToken1, 0);
         appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
         final Task task2 = createTaskInStack(stack, 1 /* userId */);
-        AppWindowToken appWindowToken2 =
+        ActivityRecord appWindowToken2 =
                 WindowTestUtils.createTestAppWindowToken(mDisplayContent);
         task2.addChild(appWindowToken2, 0);
         appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 09e5027..6a94137 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -115,4 +115,8 @@
     @Override
     public void showInsets(int types, boolean fromIme) throws RemoteException {
     }
+
+    @Override
+    public void hideInsets(int types, boolean fromIme) throws RemoteException {
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index c627c19..f44c969 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -19,17 +19,11 @@
 import static android.app.AppOpsManager.OP_NONE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.ActivityTestsBase.ActivityBuilder.createIntentAndActivityInfo;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import android.app.ActivityManager;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.os.IBinder;
-import android.util.Pair;
 import android.view.IWindow;
 import android.view.WindowManager;
 
@@ -51,23 +45,22 @@
     }
 
     /** Creates an {@link AppWindowToken} and adds it to the specified {@link Task}. */
-    static AppWindowToken createAppWindowTokenInTask(DisplayContent dc, Task task) {
-        final AppWindowToken newToken = createTestAppWindowToken(dc);
+    static ActivityRecord createAppWindowTokenInTask(DisplayContent dc, Task task) {
+        final ActivityRecord newToken = createTestAppWindowToken(dc);
         task.addChild(newToken, POSITION_TOP);
         return newToken;
     }
 
-    static AppWindowToken createTestAppWindowToken(DisplayContent dc) {
+    static ActivityRecord createTestAppWindowToken(DisplayContent dc) {
         synchronized (dc.mWmService.mGlobalLock) {
-            Pair<Intent, ActivityInfo> pair = createIntentAndActivityInfo();
-            final AppWindowToken token = new AppWindowToken(dc.mWmService,
-                    dc.mWmService.mAtmService, new ActivityRecord.Token(pair.first), pair.second,
-                    null, pair.first, dc);
-            token.setOccludesParent(true);
-            token.setHidden(false);
-            token.hiddenRequested = false;
-            spyOn(token);
-            return token;
+            final ActivityRecord r =
+                    new ActivityTestsBase.ActivityBuilder(dc.mWmService.mAtmService)
+                            .build();
+            r.onDisplayChanged(dc);
+            r.setOccludesParent(true);
+            r.setHidden(false);
+            r.hiddenRequested = false;
+            return r;
         }
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 4c4b21e..1fce46c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -204,15 +204,15 @@
         }
     }
 
-    AppWindowToken createAppWindowToken(DisplayContent dc, int windowingMode, int activityType) {
+    ActivityRecord createAppWindowToken(DisplayContent dc, int windowingMode, int activityType) {
         return createTestAppWindowToken(dc, windowingMode, activityType);
     }
 
-    AppWindowToken createTestAppWindowToken(DisplayContent dc, int
+    ActivityRecord createTestAppWindowToken(DisplayContent dc, int
             windowingMode, int activityType) {
         final TaskStack stack = createTaskStackOnDisplay(windowingMode, activityType, dc);
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final AppWindowToken appWindowToken =
+        final ActivityRecord appWindowToken =
                 WindowTestUtils.createTestAppWindowToken(dc);
         task.addChild(appWindowToken, 0);
         return appWindowToken;
@@ -244,7 +244,7 @@
 
     WindowState createAppWindow(Task task, int type, String name) {
         synchronized (mWm.mGlobalLock) {
-            final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+            final ActivityRecord token = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
             task.addChild(token, 0);
             return createWindow(null, type, token, name);
         }
diff --git a/startop/apps/test/Android.bp b/startop/apps/test/Android.bp
index a806320..2ff26b8 100644
--- a/startop/apps/test/Android.bp
+++ b/startop/apps/test/Android.bp
@@ -17,12 +17,14 @@
 android_app {
     name: "startop_test_app",
     srcs: [
+        "src/ComplexLayoutInflationActivity.java",
         "src/CPUIntensive.java",
         "src/EmptyActivity.java",
-        "src/LayoutInflationActivity.java",
-        "src/ComplexLayoutInflationActivity.java",
         "src/FrameLayoutInflationActivity.java",
+        "src/LayoutInflationActivity.java",
+        "src/NonInteractiveSystemServerBenchmarkActivity.java",
         "src/SystemServerBenchmarkActivity.java",
+        "src/SystemServerBenchmarks.java",
         "src/TextViewInflationActivity.java",
     ],
     sdk_version: "26", // Android O (8.0) and higher
diff --git a/startop/apps/test/AndroidManifest.xml b/startop/apps/test/AndroidManifest.xml
index 15785d4..ebe2584 100644
--- a/startop/apps/test/AndroidManifest.xml
+++ b/startop/apps/test/AndroidManifest.xml
@@ -84,6 +84,14 @@
             </intent-filter>
         </activity>
 
+        <activity
+            android:label="Non-interactive SystemServer Benchmark"
+            android:name=".NonInteractiveSystemServerBenchmarkActivity"
+            android:exported="true" />
+
     </application>
 
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
 </manifest>
diff --git a/startop/apps/test/README.md b/startop/apps/test/README.md
index dadc66a..949dff7 100644
--- a/startop/apps/test/README.md
+++ b/startop/apps/test/README.md
@@ -24,3 +24,14 @@
 inflation.
 
     adb shell am start -n com.android.startop.test/.ComplexLayoutInflationActivity
+
+## NonInteractiveSystemServerBenchmark
+
+This activity is for running microbenchmarks from the command line. Run as follows:
+
+   adb shell am start -W -n com.android.startop.test .NonInteractiveSystemServerBenchmarkActivity
+
+It takes awhile (and there's currently no automated way to make sure it's done),
+but when it finishes, you can get the results like this:
+
+    adb shell cat /sdcard/Android/data/com.android.startop.test/files/benchmark.csv
diff --git a/startop/apps/test/src/NonInteractiveSystemServerBenchmarkActivity.java b/startop/apps/test/src/NonInteractiveSystemServerBenchmarkActivity.java
new file mode 100644
index 0000000..a2dc2cf
--- /dev/null
+++ b/startop/apps/test/src/NonInteractiveSystemServerBenchmarkActivity.java
@@ -0,0 +1,71 @@
+/*
+ * 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.startop.test;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.GridLayout;
+import android.widget.TextView;
+
+public class NonInteractiveSystemServerBenchmarkActivity extends Activity {
+    ArrayList<CharSequence> benchmarkNames = new ArrayList();
+    ArrayList<Runnable> benchmarkThunks = new ArrayList();
+
+    PrintStream out;
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        SystemServerBenchmarks.initializeBenchmarks(this, (name, thunk) -> {
+            benchmarkNames.add(name);
+            benchmarkThunks.add(thunk);
+        });
+
+        try {
+            out = new PrintStream(new File(getExternalFilesDir(null), "benchmark.csv"));
+        } catch (FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        out.println("Name,Mean,Stdev");
+        runBenchmarks(0);
+    }
+
+    void runBenchmarks(int i) {
+        if (i < benchmarkNames.size()) {
+            SystemServerBenchmarks.runBenchmarkInBackground(benchmarkThunks.get(i),
+                    (mean, stdev) -> {
+                        out.printf("%s,%.0f,%.0f\n", benchmarkNames.get(i), mean, stdev);
+                        runBenchmarks(i + 1);
+                    });
+        }
+    }
+}
diff --git a/startop/apps/test/src/SystemServerBenchmarkActivity.java b/startop/apps/test/src/SystemServerBenchmarkActivity.java
index c8d9fde..75ea69b 100644
--- a/startop/apps/test/src/SystemServerBenchmarkActivity.java
+++ b/startop/apps/test/src/SystemServerBenchmarkActivity.java
@@ -31,13 +31,25 @@
 import android.widget.GridLayout;
 import android.widget.TextView;
 
+public class SystemServerBenchmarkActivity extends Activity implements BenchmarkRunner {
+    private GridLayout benchmarkList;
 
-class Benchmark {
-    // Time limit to run benchmarks in seconds
-    public static final int TIME_LIMIT = 5;
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.system_server_benchmark_page);
 
-    public Benchmark(ViewGroup parent, CharSequence name, Runnable thunk) {
-        Context context = parent.getContext();
+        benchmarkList = findViewById(R.id.benchmark_list);
+
+        SystemServerBenchmarks.initializeBenchmarks(this, this);
+    }
+
+    /**
+     * Adds a benchmark to the set to run.
+     *
+     * @param name A short name that shows up in the UI or benchmark results
+     */
+    public void addBenchmark(CharSequence name, Runnable thunk) {
+        Context context = benchmarkList.getContext();
         Button button = new Button(context);
         TextView mean = new TextView(context);
         TextView stdev = new TextView(context);
@@ -50,165 +62,14 @@
             mean.setText("Running...");
             stdev.setText("");
 
-            new AsyncTask() {
-                double resultMean = 0;
-                double resultStdev = 0;
-
-                @Override
-                protected Object doInBackground(Object... _args) {
-                    long startTime = System.nanoTime();
-                    int count = 0;
-
-                    // Run benchmark
-                    while (true) {
-                        long elapsed = -System.nanoTime();
-                        thunk.run();
-                        elapsed += System.nanoTime();
-
-                        count++;
-                        double elapsedVariance = (double) elapsed - resultMean;
-                        resultMean += elapsedVariance / count;
-                        resultStdev += elapsedVariance * ((double) elapsed - resultMean);
-
-                        if (System.nanoTime() - startTime > TIME_LIMIT * 1e9) {
-                            break;
-                        }
-                    }
-                    resultStdev = Math.sqrt(resultStdev / (count - 1));
-
-                    return null;
-                }
-
-                @Override
-                protected void onPostExecute(Object _result) {
-                    mean.setText(String.format("%.3f", resultMean / 1e6));
-                    stdev.setText(String.format("%.3f", resultStdev / 1e6));
-                }
-            }.execute(new Object());
-        });
-
-        parent.addView(button);
-        parent.addView(mean);
-        parent.addView(stdev);
-    }
-}
-
-public class SystemServerBenchmarkActivity extends Activity {
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.system_server_benchmark_page);
-
-        GridLayout benchmarkList = findViewById(R.id.benchmark_list);
-
-        new Benchmark(benchmarkList, "Empty", () -> {
-        });
-
-        new Benchmark(benchmarkList, "CPU Intensive (1 thread)", () -> {
-            CPUIntensive.doSomeWork(1);
-        });
-
-        new Benchmark(benchmarkList, "CPU Intensive (2 thread)", () -> {
-            CPUIntensive.doSomeWork(2);
-        });
-
-        new Benchmark(benchmarkList, "CPU Intensive (4 thread)", () -> {
-            CPUIntensive.doSomeWork(4);
-        });
-
-        new Benchmark(benchmarkList, "CPU Intensive (8 thread)", () -> {
-            CPUIntensive.doSomeWork(8);
-        });
-
-        PackageManager pm = getPackageManager();
-        new Benchmark(benchmarkList, "getInstalledApplications", () -> {
-            pm.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY);
-        });
-
-        new Benchmark(benchmarkList, "getInstalledPackages", () -> {
-            pm.getInstalledPackages(PackageManager.GET_ACTIVITIES);
-        });
-
-        new Benchmark(benchmarkList, "getPackageInfo", () -> {
-            try {
-                pm.getPackageInfo("com.android.startop.test", 0);
-            } catch (NameNotFoundException e) {
-                throw new RuntimeException(e);
-            }
-        });
-
-        new Benchmark(benchmarkList, "getApplicationInfo", () -> {
-            try {
-                pm.getApplicationInfo("com.android.startop.test", 0);
-            } catch (NameNotFoundException e) {
-                throw new RuntimeException(e);
-            }
-        });
-
-        try {
-            ApplicationInfo app = pm.getApplicationInfo("com.android.startop.test", 0);
-            new Benchmark(benchmarkList, "getResourcesForApplication", () -> {
-                try {
-                    pm.getResourcesForApplication(app);
-                } catch (NameNotFoundException e) {
-                    throw new RuntimeException(e);
-                }
+            SystemServerBenchmarks.runBenchmarkInBackground(thunk, (resultMean, resultStdev) -> {
+                mean.setText(String.format("%.3f", resultMean / 1e6));
+                stdev.setText(String.format("%.3f", resultStdev / 1e6));
             });
-
-            new Benchmark(benchmarkList, "getPackagesForUid", () -> {
-                pm.getPackagesForUid(app.uid);
-            });
-        } catch (NameNotFoundException e) {
-            throw new RuntimeException(e);
-        }
-
-        ComponentName component = new ComponentName(this, this.getClass());
-        new Benchmark(benchmarkList, "getActivityInfo", () -> {
-            try {
-                pm.getActivityInfo(component, PackageManager.GET_META_DATA);
-            } catch (NameNotFoundException e) {
-                throw new RuntimeException(e);
-            }
         });
 
-        new Benchmark(benchmarkList, "getLaunchIntentForPackage", () -> {
-            pm.getLaunchIntentForPackage("com.android.startop.test");
-        });
-
-        new Benchmark(benchmarkList, "getPackageUid", () -> {
-            try {
-                pm.getPackageUid("com.android.startop.test", 0);
-            } catch (NameNotFoundException e) {
-                throw new RuntimeException(e);
-            }
-        });
-
-        new Benchmark(benchmarkList, "checkPermission", () -> {
-            // Check for the first permission I could find.
-            pm.checkPermission("android.permission.SEND_SMS", "com.android.startop.test");
-        });
-
-        new Benchmark(benchmarkList, "checkSignatures", () -> {
-            // Compare with settings, since settings is on both AOSP and Master builds
-            pm.checkSignatures("com.android.settings", "com.android.startop.test");
-        });
-
-        Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
-        new Benchmark(benchmarkList, "queryBroadcastReceivers", () -> {
-            pm.queryBroadcastReceivers(intent, 0);
-        });
-
-        new Benchmark(benchmarkList, "hasSystemFeature", () -> {
-            pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
-        });
-
-        new Benchmark(benchmarkList, "resolveService", () -> {
-            pm.resolveService(intent, 0);
-        });
-
-        ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
-        new Benchmark(benchmarkList, "getRunningAppProcesses", () -> {
-            am.getRunningAppProcesses();
-        });
-
+        benchmarkList.addView(button);
+        benchmarkList.addView(mean);
+        benchmarkList.addView(stdev);
     }
 }
diff --git a/startop/apps/test/src/SystemServerBenchmarks.java b/startop/apps/test/src/SystemServerBenchmarks.java
new file mode 100644
index 0000000..5918503
--- /dev/null
+++ b/startop/apps/test/src/SystemServerBenchmarks.java
@@ -0,0 +1,261 @@
+/*
+ * 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.startop.test;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.ConnectivityManager;
+import android.os.AsyncTask;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.UserManager;
+
+/**
+ * An interface for running benchmarks and collecting results. Used so we can have both an
+ * interactive runner and a non-interactive runner.
+ */
+interface BenchmarkRunner {
+    void addBenchmark(CharSequence name, Runnable thunk);
+}
+
+interface ResultListener {
+    /**
+     * Called when a benchmark result is ready
+     *
+     * @param mean  The average iteration time in nanoseconds
+     * @param stdev The standard deviation of iteration times in nanoseconds
+     */
+    void onResult(double mean, double stdev);
+}
+
+class SystemServerBenchmarks {
+    // Time limit to run benchmarks in seconds
+    public static final int TIME_LIMIT = 5;
+
+    static void initializeBenchmarks(Activity parent, BenchmarkRunner benchmarks) {
+        final String packageName = parent.getPackageName();
+
+        benchmarks.addBenchmark("Empty", () -> {
+        });
+
+        benchmarks.addBenchmark("CPU Intensive (1 thread)", () -> {
+            CPUIntensive.doSomeWork(1);
+        });
+
+        benchmarks.addBenchmark("CPU Intensive (2 thread)", () -> {
+            CPUIntensive.doSomeWork(2);
+        });
+
+        benchmarks.addBenchmark("CPU Intensive (4 thread)", () -> {
+            CPUIntensive.doSomeWork(4);
+        });
+
+        benchmarks.addBenchmark("CPU Intensive (8 thread)", () -> {
+            CPUIntensive.doSomeWork(8);
+        });
+
+        PackageManager pm = parent.getPackageManager();
+        benchmarks.addBenchmark("getInstalledApplications", () -> {
+            pm.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY);
+        });
+
+        benchmarks.addBenchmark("getInstalledPackages", () -> {
+            pm.getInstalledPackages(PackageManager.GET_ACTIVITIES);
+        });
+
+        benchmarks.addBenchmark("getPackageInfo", () -> {
+            try {
+                pm.getPackageInfo(packageName, 0);
+            } catch (NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        benchmarks.addBenchmark("getApplicationInfo", () -> {
+            try {
+                pm.getApplicationInfo(packageName, 0);
+            } catch (NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        try {
+            ApplicationInfo app = pm.getApplicationInfo(packageName, 0);
+            benchmarks.addBenchmark("getResourcesForApplication", () -> {
+                try {
+                    pm.getResourcesForApplication(app);
+                } catch (NameNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+
+            benchmarks.addBenchmark("getPackagesForUid", () -> {
+                pm.getPackagesForUid(app.uid);
+            });
+        } catch (NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+
+        ComponentName component = new ComponentName(parent, parent.getClass());
+        benchmarks.addBenchmark("getActivityInfo", () -> {
+            try {
+                pm.getActivityInfo(component, PackageManager.GET_META_DATA);
+            } catch (NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        benchmarks.addBenchmark("getLaunchIntentForPackage", () -> {
+            pm.getLaunchIntentForPackage(packageName);
+        });
+
+        benchmarks.addBenchmark("getPackageUid", () -> {
+            try {
+                pm.getPackageUid(packageName, 0);
+            } catch (NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        benchmarks.addBenchmark("checkPermission", () -> {
+            // Check for the first permission I could find.
+            pm.checkPermission("android.permission.SEND_SMS", packageName);
+        });
+
+        benchmarks.addBenchmark("checkSignatures", () -> {
+            // Compare with settings, since settings is on both AOSP and Master builds
+            pm.checkSignatures("com.android.settings", packageName);
+        });
+
+        Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
+        benchmarks.addBenchmark("queryBroadcastReceivers", () -> {
+            pm.queryBroadcastReceivers(intent, 0);
+        });
+
+        benchmarks.addBenchmark("hasSystemFeature", () -> {
+            pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
+        });
+
+        benchmarks.addBenchmark("resolveService", () -> {
+            pm.resolveService(intent, 0);
+        });
+
+        ActivityManager am = (ActivityManager) parent.getSystemService(Context.ACTIVITY_SERVICE);
+        benchmarks.addBenchmark("getRunningAppProcesses", () -> {
+            am.getRunningAppProcesses();
+        });
+
+        // We use PendingIntent.getCreatorPackage, since
+        // getPackageIntentForSender is not public to us, but getCreatorPackage
+        // is just a thin wrapper around it.
+        PendingIntent pi = PendingIntent.getActivity(parent, 0, new Intent(), 0);
+        benchmarks.addBenchmark("getPackageIntentForSender", () -> {
+            pi.getCreatorPackage();
+        });
+
+        PowerManager pwr = (PowerManager) parent.getSystemService(Context.POWER_SERVICE);
+        PowerManager.WakeLock wl = pwr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "benchmark tag");
+        benchmarks.addBenchmark("WakeLock Acquire/Release", () -> {
+            wl.acquire();
+            wl.release();
+        });
+
+        AppOpsManager appOps = (AppOpsManager) parent.getSystemService(Context.APP_OPS_SERVICE);
+        int uid = Process.myUid();
+        benchmarks.addBenchmark("AppOpsService.checkOperation", () -> {
+            appOps.checkOp(AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, uid, packageName);
+        });
+
+        benchmarks.addBenchmark("AppOpsService.checkPackage", () -> {
+            appOps.checkPackage(uid, packageName);
+        });
+
+        benchmarks.addBenchmark("AppOpsService.noteOperation", () -> {
+            appOps.noteOp(AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, uid, packageName);
+        });
+
+        benchmarks.addBenchmark("AppOpsService.noteProxyOperation", () -> {
+            appOps.noteProxyOp(AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, packageName);
+        });
+
+        UserManager userManager = (UserManager) parent.getSystemService(Context.USER_SERVICE);
+        benchmarks.addBenchmark("isUserUnlocked", () -> {
+            userManager.isUserUnlocked();
+        });
+
+        benchmarks.addBenchmark("getIntentSender", () -> {
+            pi.getIntentSender();
+        });
+
+        ConnectivityManager cm = (ConnectivityManager) parent
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        benchmarks.addBenchmark("getActiveNetworkInfo", () -> {
+            cm.getActiveNetworkInfo();
+        });
+    }
+
+    /**
+     * A helper method for benchark runners to actually run the benchmark and gather stats
+     *
+     * @param thunk    The code whose performance we want to measure
+     * @param reporter What to do with the results
+     */
+    static void runBenchmarkInBackground(Runnable thunk, ResultListener reporter) {
+        new AsyncTask() {
+            double resultMean = 0;
+            double resultStdev = 0;
+
+            @Override
+            protected Object doInBackground(Object... _args) {
+                long startTime = System.nanoTime();
+                int count = 0;
+
+                // Run benchmark
+                while (true) {
+                    long elapsed = -System.nanoTime();
+                    thunk.run();
+                    elapsed += System.nanoTime();
+
+                    count++;
+                    double elapsedVariance = (double) elapsed - resultMean;
+                    resultMean += elapsedVariance / count;
+                    resultStdev += elapsedVariance * ((double) elapsed - resultMean);
+
+                    if (System.nanoTime() - startTime > TIME_LIMIT * 1e9) {
+                        break;
+                    }
+                }
+                resultStdev = Math.sqrt(resultStdev / (count - 1));
+
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Object _result) {
+                reporter.onResult(resultMean, resultStdev);
+            }
+        }.execute(new Object());
+    }
+}
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 acf9946..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;
 
 /**
@@ -86,10 +87,14 @@
     public static final class IntentStarted extends AppLaunchEvent {
         @NonNull
         public final Intent intent;
+        public final long timestampNs;
 
-        public IntentStarted(@SequenceId long sequenceId, Intent intent) {
+        public IntentStarted(@SequenceId long sequenceId,
+                             Intent intent,
+                             long timestampNs) {
             super(sequenceId);
             this.intent = intent;
+            this.timestampNs = timestampNs;
 
             Objects.requireNonNull(intent, "intent");
         }
@@ -98,14 +103,16 @@
         public boolean equals(Object other) {
             if (other instanceof IntentStarted) {
                 return intent.equals(((IntentStarted)other).intent) &&
-                        super.equals(other);
+                       timestampNs == ((IntentStarted)other).timestampNs &&
+                       super.equals(other);
             }
             return false;
         }
 
         @Override
         protected String toStringBody() {
-            return ", intent=" + intent.toString();
+            return ", intent=" + intent.toString() +
+                   " , timestampNs=" + Long.toString(timestampNs);
         }
 
 
@@ -113,11 +120,13 @@
         protected void writeToParcelImpl(Parcel p, int flags) {
             super.writeToParcelImpl(p, flags);
             IntentProtoParcelable.write(p, intent, flags);
+            p.writeLong(timestampNs);
         }
 
         IntentStarted(Parcel p) {
             super(p);
             intent = IntentProtoParcelable.create(p);
+            timestampNs = p.readLong();
         }
     }
 
@@ -154,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;
@@ -163,7 +172,7 @@
 
         @Override
         protected String toStringBody() {
-            return ", " + activityRecordSnapshot.toString();
+            return ", " + new String(activityRecordSnapshot);
         }
 
         @Override
@@ -200,7 +209,7 @@
 
         @Override
         protected String toStringBody() {
-            return ", temperature=" + Integer.toString(temperature);
+            return super.toStringBody() + ", temperature=" + Integer.toString(temperature);
         }
 
         @Override
@@ -216,18 +225,39 @@
     }
 
     public static final class ActivityLaunchFinished extends BaseWithActivityRecordData {
+        public final long timestampNs;
+
         public ActivityLaunchFinished(@SequenceId long sequenceId,
-                @NonNull @ActivityRecordProto byte[] snapshot) {
+                @NonNull @ActivityRecordProto byte[] snapshot,
+                long timestampNs) {
             super(sequenceId, snapshot);
+            this.timestampNs = timestampNs;
         }
 
         @Override
         public boolean equals(Object other) {
-            if (other instanceof ActivityLaunched) {
-                return super.equals(other);
+            if (other instanceof ActivityLaunchFinished) {
+                return timestampNs == ((ActivityLaunchFinished)other).timestampNs &&
+                       super.equals(other);
             }
             return false;
         }
+
+        @Override
+        protected String toStringBody() {
+            return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
+        }
+
+        @Override
+        protected void writeToParcelImpl(Parcel p, int flags) {
+           super.writeToParcelImpl(p, flags);
+           p.writeLong(timestampNs);
+        }
+
+        ActivityLaunchFinished(Parcel p) {
+            super(p);
+            timestampNs = p.readLong();
+        }
     }
 
      public static class ActivityLaunchCancelled extends AppLaunchEvent {
@@ -242,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;
@@ -251,7 +281,7 @@
 
         @Override
         protected String toStringBody() {
-            return ", " + activityRecordSnapshot.toString();
+            return super.toStringBody() + ", " + new String(activityRecordSnapshot);
         }
 
         @Override
@@ -275,6 +305,42 @@
         }
     }
 
+    public static final class ReportFullyDrawn extends BaseWithActivityRecordData {
+        public final long timestampNs;
+
+        public ReportFullyDrawn(@SequenceId long sequenceId,
+                @NonNull @ActivityRecordProto byte[] snapshot,
+                long timestampNs) {
+            super(sequenceId, snapshot);
+            this.timestampNs = timestampNs;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof ReportFullyDrawn) {
+                return timestampNs == ((ReportFullyDrawn)other).timestampNs &&
+                        super.equals(other);
+            }
+            return false;
+        }
+
+        @Override
+        protected String toStringBody() {
+            return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
+        }
+
+        @Override
+        protected void writeToParcelImpl(Parcel p, int flags) {
+           super.writeToParcelImpl(p, flags);
+           p.writeLong(timestampNs);
+        }
+
+        ReportFullyDrawn(Parcel p) {
+            super(p);
+            timestampNs = p.readLong();
+        }
+    }
+
     @Override
     public @ContentsFlags int describeContents() { return 0; }
 
@@ -348,6 +414,7 @@
             ActivityLaunched.class,
             ActivityLaunchFinished.class,
             ActivityLaunchCancelled.class,
+            ReportFullyDrawn.class,
     };
 
     public static class ActivityRecordProtoParcelable {
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index 902da4c..f753548 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -315,19 +315,19 @@
         // All callbacks occur on the same background thread. Don't synchronize explicitly.
 
         @Override
-        public void onIntentStarted(@NonNull Intent intent) {
+        public void onIntentStarted(@NonNull Intent intent, long timestampNs) {
             // #onIntentStarted [is the only transition that] initiates a new launch sequence.
             ++mSequenceId;
 
             if (DEBUG) {
-                Log.v(TAG, String.format("AppLaunchObserver#onIntentStarted(%d, %s)",
-                        mSequenceId, intent));
+                Log.v(TAG, String.format("AppLaunchObserver#onIntentStarted(%d, %s, %d)",
+                        mSequenceId, intent, timestampNs));
             }
 
             invokeRemote(mIorapRemote,
                 (IIorap remote) ->
                     remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
-                        new AppLaunchEvent.IntentStarted(mSequenceId, intent))
+                        new AppLaunchEvent.IntentStarted(mSequenceId, intent, timestampNs))
             );
         }
 
@@ -374,16 +374,34 @@
         }
 
         @Override
-        public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity) {
+        public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity,
+            long timestampNs) {
             if (DEBUG) {
-                Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchFinished(%d, %s)",
-                        mSequenceId, activity));
+                Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchFinished(%d, %s, %d)",
+                        mSequenceId, activity, timestampNs));
             }
 
             invokeRemote(mIorapRemote,
                 (IIorap remote) ->
                     remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
-                        new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, activity))
+                        new AppLaunchEvent.ActivityLaunchFinished(mSequenceId,
+                            activity,
+                            timestampNs))
+            );
+        }
+
+        @Override
+        public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
+            long timestampNs) {
+            if (DEBUG) {
+                Log.v(TAG, String.format("AppLaunchObserver#onReportFullyDrawn(%d, %s, %d)",
+                        mSequenceId, activity, timestampNs));
+            }
+
+            invokeRemote(mIorapRemote,
+                (IIorap remote) ->
+                    remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+                        new AppLaunchEvent.ReportFullyDrawn(mSequenceId, activity, timestampNs))
             );
         }
     }
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/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1d5c18d..9e60afc 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2817,7 +2817,7 @@
             "ping_test_before_data_switch_bool";
 
     /**
-     * Controls time in milli seconds until DcTracker reevaluates 5G connection state.
+     * Controls time in milliseconds until DcTracker reevaluates 5G connection state.
      * @hide
      */
     public static final String KEY_5G_WATCHDOG_TIME_MS_LONG =
@@ -3199,6 +3199,13 @@
     public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY =
             "carrier_certificate_string_array";
 
+    /**
+     * DisconnectCause array to play busy tone. Value should be array of
+     * {@link android.telephony.DisconnectCause}.
+     */
+    public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY =
+            "disconnect_cause_play_busytone_int_array";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -3628,6 +3635,8 @@
         sDefaults.putBoolean(KEY_SUPPORT_WPS_OVER_IMS_BOOL, true);
         sDefaults.putAll(Ims.getDefaults());
         sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, null);
+        sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
+                new int[] {4 /* BUSY */});
     }
 
     /**
diff --git a/telephony/java/android/telephony/CellBroadcastService.java b/telephony/java/android/telephony/CellBroadcastService.java
index d5e447e..46eb9df 100644
--- a/telephony/java/android/telephony/CellBroadcastService.java
+++ b/telephony/java/android/telephony/CellBroadcastService.java
@@ -21,6 +21,7 @@
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
+import android.telephony.cdma.CdmaSmsCbProgramData;
 
 /**
  * A service which exposes the cell broadcast handling module to the system.
@@ -69,9 +70,11 @@
     /**
      * Handle a CDMA cell broadcast SMS message forwarded from the system.
      * @param slotIndex the index of the slot which received the message
-     * @param message the SMS PDU
+     * @param bearerData the CDMA SMS bearer data
+     * @param serviceCategory the CDMA SCPT service category
      */
-    public abstract void onCdmaCellBroadcastSms(int slotIndex, byte[] message);
+    public abstract void onCdmaCellBroadcastSms(int slotIndex, byte[] bearerData,
+            @CdmaSmsCbProgramData.Category int serviceCategory);
 
     /**
      * If overriding this method, call through to the super method for any unknown actions.
@@ -102,11 +105,14 @@
         /**
          * Handle a CDMA cell broadcast SMS.
          * @param slotIndex the index of the slot which received the broadcast
-         * @param message the SMS message PDU
+         * @param bearerData the CDMA SMS bearer data
+         * @param serviceCategory the CDMA SCPT service category
          */
         @Override
-        public void handleCdmaCellBroadcastSms(int slotIndex, byte[] message) {
-            CellBroadcastService.this.onCdmaCellBroadcastSms(slotIndex, message);
+        public void handleCdmaCellBroadcastSms(int slotIndex, byte[] bearerData,
+                int serviceCategory) {
+            CellBroadcastService.this.onCdmaCellBroadcastSms(slotIndex, bearerData,
+                    serviceCategory);
         }
     }
 }
diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
index cdf01da..4dc54f0 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -245,9 +245,12 @@
     }
 
     /**
-     * Get the Ec/No as dB
+     * Get the Ec/No (Energy per chip over the noise spectral density) as dB.
      *
-     * @hide
+     * Reference: TS 25.133 Section 9.1.2.3
+     *
+     * @return the Ec/No of the measured cell in the range [-24, 1] or
+     * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable
      */
     public int getEcNo() {
         return mEcNo;
diff --git a/telephony/java/android/telephony/ICellBroadcastService.aidl b/telephony/java/android/telephony/ICellBroadcastService.aidl
index eff64a2..bcd6cc5 100644
--- a/telephony/java/android/telephony/ICellBroadcastService.aidl
+++ b/telephony/java/android/telephony/ICellBroadcastService.aidl
@@ -28,5 +28,5 @@
     oneway void handleGsmCellBroadcastSms(int slotId, in byte[] message);
 
     /** @see android.telephony.CellBroadcastService#onCdmaCellBroadcastSms */
-    oneway void handleCdmaCellBroadcastSms(int slotId, in byte[] message);
+    oneway void handleCdmaCellBroadcastSms(int slotId, in byte[] bearerData, int serviceCategory);
 }
diff --git a/telephony/java/android/telephony/ICellInfoCallback.aidl b/telephony/java/android/telephony/ICellInfoCallback.aidl
index ee3c1b1..60732a3 100644
--- a/telephony/java/android/telephony/ICellInfoCallback.aidl
+++ b/telephony/java/android/telephony/ICellInfoCallback.aidl
@@ -16,7 +16,6 @@
 
 package android.telephony;
 
-import android.os.ParcelableException;
 import android.telephony.CellInfo;
 
 import java.util.List;
@@ -28,5 +27,5 @@
 oneway interface ICellInfoCallback
 {
     void onCellInfo(in List<CellInfo> state);
-    void onError(in int errorCode, in ParcelableException detail);
+    void onError(in int errorCode, in String exceptionName, in String message);
 }
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index f4330fa..2d35f8e 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -1814,6 +1814,36 @@
 
     // SMS send failure result codes
 
+    /** @hide */
+    @IntDef(prefix = { "RESULT" }, value = {
+            RESULT_ERROR_NONE,
+            RESULT_ERROR_GENERIC_FAILURE,
+            RESULT_ERROR_RADIO_OFF,
+            RESULT_ERROR_NULL_PDU,
+            RESULT_ERROR_NO_SERVICE,
+            RESULT_ERROR_LIMIT_EXCEEDED,
+            RESULT_ERROR_FDN_CHECK_FAILURE,
+            RESULT_ERROR_SHORT_CODE_NOT_ALLOWED,
+            RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED,
+            RESULT_RADIO_NOT_AVAILABLE,
+            RESULT_NETWORK_REJECT,
+            RESULT_INVALID_ARGUMENTS,
+            RESULT_INVALID_STATE,
+            RESULT_NO_MEMORY,
+            RESULT_INVALID_SMS_FORMAT,
+            RESULT_SYSTEM_ERROR,
+            RESULT_MODEM_ERROR,
+            RESULT_NETWORK_ERROR,
+            RESULT_INVALID_SMSC_ADDRESS,
+            RESULT_OPERATION_NOT_ALLOWED,
+            RESULT_INTERNAL_ERROR,
+            RESULT_NO_RESOURCES,
+            RESULT_CANCELLED,
+            RESULT_REQUEST_NOT_SUPPORTED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Result {}
+
     /**
      * No error.
      * @hide
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 1b87657..40d057f 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -53,7 +53,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.telephony.Annotation.NetworkType;
 import android.telephony.euicc.EuiccManager;
 import android.telephony.ims.ImsMmTelManager;
 import android.util.DisplayMetrics;
@@ -2101,13 +2100,13 @@
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public static boolean isValidSlotIndex(int slotIndex) {
-        return slotIndex >= 0 && slotIndex < TelephonyManager.getDefault().getMaxPhoneCount();
+        return slotIndex >= 0 && slotIndex < TelephonyManager.getDefault().getSupportedModemCount();
     }
 
     /** @hide */
     @UnsupportedAppUsage
     public static boolean isValidPhoneId(int phoneId) {
-        return phoneId >= 0 && phoneId < TelephonyManager.getDefault().getMaxPhoneCount();
+        return phoneId >= 0 && phoneId < TelephonyManager.getDefault().getSupportedModemCount();
     }
 
     /** @hide */
@@ -2420,8 +2419,12 @@
      * @param plans the list of plans. The first plan is always the primary and
      *            most important plan. Any additional plans are secondary and
      *            may not be displayed or used by decision making logic.
+     *            The list of all plans must meet the requirements defined in
+     *            {@link SubscriptionPlan.Builder#setNetworkTypes(int[])}.
      * @throws SecurityException if the caller doesn't meet the requirements
      *             outlined above.
+     * @throws IllegalArgumentException if plans don't meet the requirements
+     *             mentioned above.
      */
     public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans) {
         try {
@@ -2466,51 +2469,10 @@
      */
     public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered,
             @DurationMillisLong long timeoutMillis) {
-        setSubscriptionOverrideUnmetered(subId, null, overrideUnmetered, timeoutMillis);
-    }
-
-    /**
-     * Temporarily override the billing relationship between a carrier and
-     * a specific subscriber to be considered unmetered for the given network
-     * types. This will be reflected to apps via
-     * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED}.
-     * This method is only accessible to the following narrow set of apps:
-     * <ul>
-     * <li>The carrier app for this subscriberId, as determined by
-     * {@link TelephonyManager#hasCarrierPrivileges()}.
-     * <li>The carrier app explicitly delegated access through
-     * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
-     * </ul>
-     *
-     * @param subId the subscriber this override applies to.
-     * @param networkTypes all network types to set an override for. A null
-     *            network type means to apply the override to all network types.
-     *            Any unspecified network types will default to metered.
-     * @param overrideUnmetered set if the billing relationship should be
-     *            considered unmetered.
-     * @param timeoutMillis the timeout after which the requested override will
-     *            be automatically cleared, or {@code 0} to leave in the
-     *            requested state until explicitly cleared, or the next reboot,
-     *            whichever happens first.
-     * @throws SecurityException if the caller doesn't meet the requirements
-     *            outlined above.
-     * {@hide}
-     */
-    public void setSubscriptionOverrideUnmetered(int subId,
-            @Nullable @NetworkType int[] networkTypes, boolean overrideUnmetered,
-            @DurationMillisLong long timeoutMillis) {
         try {
-            long networkTypeMask = 0;
-            if (networkTypes != null) {
-                for (int networkType : networkTypes) {
-                    networkTypeMask |= TelephonyManager.getBitMaskForNetworkType(networkType);
-                }
-            } else {
-                networkTypeMask = ~0;
-            }
             final int overrideValue = overrideUnmetered ? OVERRIDE_UNMETERED : 0;
             getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_UNMETERED, overrideValue,
-                    networkTypeMask, timeoutMillis, mContext.getOpPackageName());
+                    timeoutMillis, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2542,52 +2504,10 @@
      */
     public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested,
             @DurationMillisLong long timeoutMillis) {
-        setSubscriptionOverrideCongested(subId, null, overrideCongested, timeoutMillis);
-    }
-
-    /**
-     * Temporarily override the billing relationship plan between a carrier and
-     * a specific subscriber to be considered congested. This will cause the
-     * device to delay certain network requests when possible, such as developer
-     * jobs that are willing to run in a flexible time window.
-     * <p>
-     * This method is only accessible to the following narrow set of apps:
-     * <ul>
-     * <li>The carrier app for this subscriberId, as determined by
-     * {@link TelephonyManager#hasCarrierPrivileges()}.
-     * <li>The carrier app explicitly delegated access through
-     * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
-     * </ul>
-     *
-     * @param subId the subscriber this override applies to.
-     * @param networkTypes all network types to set an override for. A null
-     *            network type means to apply the override to all network types.
-     *            Any unspecified network types will default to not congested.
-     * @param overrideCongested set if the subscription should be considered
-     *            congested.
-     * @param timeoutMillis the timeout after which the requested override will
-     *            be automatically cleared, or {@code 0} to leave in the
-     *            requested state until explicitly cleared, or the next reboot,
-     *            whichever happens first.
-     * @throws SecurityException if the caller doesn't meet the requirements
-     *             outlined above.
-     * @hide
-     */
-    public void setSubscriptionOverrideCongested(int subId,
-            @Nullable @NetworkType int[] networkTypes, boolean overrideCongested,
-            @DurationMillisLong long timeoutMillis) {
         try {
-            long networkTypeMask = 0;
-            if (networkTypes != null) {
-                for (int networkType : networkTypes) {
-                    networkTypeMask |= TelephonyManager.getBitMaskForNetworkType(networkType);
-                }
-            } else {
-                networkTypeMask = ~0;
-            }
             final int overrideValue = overrideCongested ? OVERRIDE_CONGESTED : 0;
             getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_CONGESTED, overrideValue,
-                    networkTypeMask, timeoutMillis, mContext.getOpPackageName());
+                    timeoutMillis, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/telephony/java/android/telephony/SubscriptionPlan.java b/telephony/java/android/telephony/SubscriptionPlan.java
index ec2050f..e24eb26 100644
--- a/telephony/java/android/telephony/SubscriptionPlan.java
+++ b/telephony/java/android/telephony/SubscriptionPlan.java
@@ -24,6 +24,7 @@
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.Annotation.NetworkType;
 import android.util.Range;
 import android.util.RecurrenceRule;
 
@@ -33,6 +34,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.time.Period;
 import java.time.ZonedDateTime;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.Objects;
 
@@ -80,6 +82,8 @@
     private int dataLimitBehavior = LIMIT_BEHAVIOR_UNKNOWN;
     private long dataUsageBytes = BYTES_UNKNOWN;
     private long dataUsageTime = TIME_UNKNOWN;
+    private @NetworkType int[] networkTypes;
+    private long networkTypesBitMask;
 
     private SubscriptionPlan(RecurrenceRule cycleRule) {
         this.cycleRule = Preconditions.checkNotNull(cycleRule);
@@ -93,6 +97,7 @@
         dataLimitBehavior = source.readInt();
         dataUsageBytes = source.readLong();
         dataUsageTime = source.readLong();
+        networkTypes = source.createIntArray();
     }
 
     @Override
@@ -109,6 +114,7 @@
         dest.writeInt(dataLimitBehavior);
         dest.writeLong(dataUsageBytes);
         dest.writeLong(dataUsageTime);
+        dest.writeIntArray(networkTypes);
     }
 
     @Override
@@ -121,13 +127,14 @@
                 .append(" dataLimitBehavior=").append(dataLimitBehavior)
                 .append(" dataUsageBytes=").append(dataUsageBytes)
                 .append(" dataUsageTime=").append(dataUsageTime)
+                .append(" networkTypes=").append(Arrays.toString(networkTypes))
                 .append("}").toString();
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(cycleRule, title, summary, dataLimitBytes, dataLimitBehavior,
-                dataUsageBytes, dataUsageTime);
+                dataUsageBytes, dataUsageTime, Arrays.hashCode(networkTypes));
     }
 
     @Override
@@ -140,7 +147,8 @@
                     && dataLimitBytes == other.dataLimitBytes
                     && dataLimitBehavior == other.dataLimitBehavior
                     && dataUsageBytes == other.dataUsageBytes
-                    && dataUsageTime == other.dataUsageTime;
+                    && dataUsageTime == other.dataUsageTime
+                    && Arrays.equals(networkTypes, other.networkTypes);
         }
         return false;
     }
@@ -204,6 +212,32 @@
     }
 
     /**
+     * Return an array containing all {@link NetworkType}s this SubscriptionPlan applies to.
+     * A null array means this SubscriptionPlan applies to all network types.
+     */
+    public @Nullable @NetworkType int[] getNetworkTypes() {
+        return networkTypes;
+    }
+
+    /**
+     * Return the networkTypes array converted to a {@link TelephonyManager.NetworkTypeBitMask}
+     * @hide
+     */
+    public long getNetworkTypesBitMask() {
+        // calculate bitmask the first time and save for future calls
+        if (networkTypesBitMask == 0) {
+            if (networkTypes == null) {
+                networkTypesBitMask = ~0;
+            } else {
+                for (int networkType : networkTypes) {
+                    networkTypesBitMask |= TelephonyManager.getBitMaskForNetworkType(networkType);
+                }
+            }
+        }
+        return networkTypesBitMask;
+    }
+
+    /**
      * Return an iterator that will return all valid data usage cycles based on
      * any recurrence rules. The iterator starts from the currently active cycle
      * and walks backwards through time.
@@ -335,5 +369,24 @@
             plan.dataUsageTime = dataUsageTime;
             return this;
         }
+
+        /**
+         * Set the network types this SubscriptionPlan applies to.
+         * The developer must supply at least one plan that applies to all network types (default),
+         * and all additional plans may not include a particular network type more than once.
+         * Plan selection will prefer plans that have specific network types defined
+         * over plans that apply to all network types.
+         *
+         * @param networkTypes a set of all {@link NetworkType}s that apply to this plan.
+         *            A null value or empty array means the plan applies to all network types.
+         */
+        public @NonNull Builder setNetworkTypes(@Nullable @NetworkType int[] networkTypes) {
+            if (networkTypes == null || networkTypes.length == 0) {
+                plan.networkTypes = null;
+            } else {
+                plan.networkTypes = networkTypes;
+            }
+            return this;
+        }
     }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2442023..03e57e7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -65,7 +65,6 @@
 import android.telecom.TelecomManager;
 import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.CallState;
-import android.telephony.Annotation.DataState;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SimActivationState;
@@ -283,6 +282,21 @@
     };
 
     /** @hide */
+    @IntDef(prefix = {"MODEM_COUNT_"},
+            value = {
+                    MODEM_COUNT_NO_MODEM,
+                    MODEM_COUNT_SINGLE_MODEM,
+                    MODEM_COUNT_DUAL_MODEM,
+                    MODEM_COUNT_TRI_MODEM
+            })
+    public @interface ModemCount {}
+
+    public static final int MODEM_COUNT_NO_MODEM     = 0;
+    public static final int MODEM_COUNT_SINGLE_MODEM = 1;
+    public static final int MODEM_COUNT_DUAL_MODEM   = 2;
+    public static final int MODEM_COUNT_TRI_MODEM    = 3;
+
+    /** @hide */
     @UnsupportedAppUsage
     public TelephonyManager(Context context) {
       this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
@@ -359,12 +373,26 @@
     /**
      * Returns the number of phones available.
      * Returns 0 if none of voice, sms, data is not supported
-     * Returns 1 for Single standby mode (Single SIM functionality)
-     * Returns 2 for Dual standby mode.(Dual SIM functionality)
-     * Returns 3 for Tri standby mode.(Tri SIM functionality)
+     * Returns 1 for Single standby mode (Single SIM functionality).
+     * Returns 2 for Dual standby mode (Dual SIM functionality).
+     * Returns 3 for Tri standby mode (Tri SIM functionality).
+     * @deprecated Use {@link #getActiveModemCount} instead.
      */
+    @Deprecated
     public int getPhoneCount() {
-        int phoneCount = 1;
+        return getActiveModemCount();
+    }
+
+    /**
+     * Returns the number of logical modems currently configured to be activated.
+     *
+     * Returns 0 if none of voice, sms, data is not supported
+     * Returns 1 for Single standby mode (Single SIM functionality).
+     * Returns 2 for Dual standby mode (Dual SIM functionality).
+     * Returns 3 for Tri standby mode (Tri SIM functionality).
+     */
+    public @ModemCount int getActiveModemCount() {
+        int modemCount = 1;
         switch (getMultiSimConfiguration()) {
             case UNKNOWN:
                 ConnectivityManager cm = mContext == null ? null : (ConnectivityManager) mContext
@@ -372,33 +400,30 @@
                 // check for voice and data support, 0 if not supported
                 if (!isVoiceCapable() && !isSmsCapable() && cm != null
                         && !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
-                    phoneCount = 0;
+                    modemCount = MODEM_COUNT_NO_MODEM;
                 } else {
-                    phoneCount = 1;
+                    modemCount = MODEM_COUNT_SINGLE_MODEM;
                 }
                 break;
             case DSDS:
             case DSDA:
-                phoneCount = PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
+                modemCount = MODEM_COUNT_DUAL_MODEM;
                 break;
             case TSTS:
-                phoneCount = PhoneConstants.MAX_PHONE_COUNT_TRI_SIM;
+                modemCount = MODEM_COUNT_TRI_MODEM;
                 break;
         }
-        return phoneCount;
+        return modemCount;
     }
 
     /**
-     *
-     * Return how many phone / logical modem can be active simultaneously, in terms of device
+     * Return how many logical modem can be potentially active simultaneously, in terms of hardware
      * capability.
-     * For example, for a dual-SIM capable device, it always returns 2, even if only one logical
-     * modem / SIM is active (aka in single SIM mode).
-     *
-     * TODO: b/139642279 publicize and rename.
-     * @hide
+     * It might return different value from {@link #getActiveModemCount}. For example, for a
+     * dual-SIM capable device operating in single SIM mode (only one logical modem is turned on),
+     * {@link #getActiveModemCount} returns 1 while this API returns 2.
      */
-    public int getMaxPhoneCount() {
+    public @ModemCount int getSupportedModemCount() {
         // TODO: b/139642279 when turning on this feature, remove dependency of
         // PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE and always return result based on
         // PROPERTY_MAX_ACTIVE_MODEMS.
@@ -5545,18 +5570,20 @@
             telephony.requestCellInfoUpdate(
                     getSubId(),
                     new ICellInfoCallback.Stub() {
+                        @Override
                         public void onCellInfo(List<CellInfo> cellInfo) {
                             Binder.withCleanCallingIdentity(() ->
                                     executor.execute(() -> callback.onCellInfo(cellInfo)));
                         }
 
-                        public void onError(int errorCode, android.os.ParcelableException detail) {
+                        @Override
+                        public void onError(int errorCode, String exceptionName, String message) {
                             Binder.withCleanCallingIdentity(() ->
                                     executor.execute(() -> callback.onError(
-                                            errorCode, detail.getCause())));
+                                            errorCode,
+                                            createThrowableByClassName(exceptionName, message))));
                         }
                     }, getOpPackageName());
-
         } catch (RemoteException ex) {
         }
     }
@@ -5585,21 +5612,36 @@
             telephony.requestCellInfoUpdateWithWorkSource(
                     getSubId(),
                     new ICellInfoCallback.Stub() {
+                        @Override
                         public void onCellInfo(List<CellInfo> cellInfo) {
                             Binder.withCleanCallingIdentity(() ->
                                     executor.execute(() -> callback.onCellInfo(cellInfo)));
                         }
 
-                        public void onError(int errorCode, android.os.ParcelableException detail) {
+                        @Override
+                        public void onError(int errorCode, String exceptionName, String message) {
                             Binder.withCleanCallingIdentity(() ->
                                     executor.execute(() -> callback.onError(
-                                            errorCode, detail.getCause())));
+                                            errorCode,
+                                            createThrowableByClassName(exceptionName, message))));
                         }
                     }, getOpPackageName(), workSource);
         } catch (RemoteException ex) {
         }
     }
 
+    private static Throwable createThrowableByClassName(String className, String message) {
+        if (className == null) {
+            return null;
+        }
+        try {
+            Class<?> c = Class.forName(className);
+            return (Throwable) c.getConstructor(String.class).newInstance(message);
+        } catch (ReflectiveOperationException | ClassCastException e) {
+        }
+        return new RuntimeException(className + ": " + message);
+    }
+
     /**
      * Sets the minimum time in milli-seconds between {@link PhoneStateListener#onCellInfoChanged
      * PhoneStateListener.onCellInfoChanged} will be invoked.
diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java
index 1e0d9a78..10251d7 100644
--- a/telephony/java/android/telephony/ims/ImsReasonInfo.java
+++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java
@@ -885,6 +885,12 @@
      */
     public static final int CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION = 1623;
 
+    /**
+     * The dialed RTT call should be retried without RTT
+     * @hide
+     */
+    public static final int CODE_RETRY_ON_IMS_WITHOUT_RTT = 3001;
+
     /*
      * OEM specific error codes. To be used by OEMs when they don't want to reveal error code which
      * would be replaced by ERROR_UNSPECIFIED.
@@ -1065,6 +1071,7 @@
             CODE_REJECT_VT_AVPF_NOT_ALLOWED,
             CODE_REJECT_ONGOING_ENCRYPTED_CALL,
             CODE_REJECT_ONGOING_CS_CALL,
+            CODE_RETRY_ON_IMS_WITHOUT_RTT,
             CODE_OEM_CAUSE_1,
             CODE_OEM_CAUSE_2,
             CODE_OEM_CAUSE_3,
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index 175769b..36ece95 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -17,6 +17,7 @@
 package android.telephony.ims.stub;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.SystemApi;
 import android.os.RemoteException;
 import android.telephony.SmsManager;
@@ -148,14 +149,16 @@
      *
      * @param token unique token generated by the platform that should be used when triggering
      *             callbacks for this specific message.
-     * @param messageRef the message reference.
-     * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
-     *               {@link SmsMessage#FORMAT_3GPP2}.
+     * @param messageRef the message reference, which may be 1 byte if it is in
+     *     {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+     *     {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+     * @param format the format of the message.
      * @param smsc the Short Message Service Center address.
      * @param isRetry whether it is a retry of an already attempted message or not.
      * @param pdu PDU representing the contents of the message.
      */
-    public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
+    public void sendSms(int token, @IntRange(from = 0, to = 65535) int messageRef,
+            @SmsMessage.Format String format, String smsc, boolean isRetry,
             byte[] pdu) {
         // Base implementation returns error. Should be overridden.
         try {
@@ -172,14 +175,13 @@
      * provider.
      *
      * @param token token provided in {@link #onSmsReceived(int, String, byte[])}
-     * @param messageRef the message reference
-     * @param result result of delivering the message. Valid values are:
-     *  {@link #DELIVER_STATUS_OK},
-     *  {@link #DELIVER_STATUS_ERROR_GENERIC},
-     *  {@link #DELIVER_STATUS_ERROR_NO_MEMORY},
-     *  {@link #DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED}
+     * @param messageRef the message reference, which may be 1 byte if it is in
+     *     {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+     *     {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+     * @param result result of delivering the message.
      */
-    public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
+    public void acknowledgeSms(int token, @IntRange(from = 0, to = 65535)  int messageRef,
+            @DeliverStatusResult int result) {
         Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
     }
 
@@ -191,12 +193,13 @@
      *
      * @param token token provided in {@link #onSmsStatusReportReceived(int, int, String, byte[])}
      *              or {@link #onSmsStatusReportReceived(int, String, byte[])}
-     * @param messageRef the message reference
-     * @param result result of delivering the message. Valid values are:
-     *  {@link #STATUS_REPORT_STATUS_OK},
-     *  {@link #STATUS_REPORT_STATUS_ERROR}
+     * @param messageRef the message reference, which may be 1 byte if it is in
+     *     {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+     *     {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+     * @param result result of delivering the message.
      */
-    public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
+    public void acknowledgeSmsReport(int token, @IntRange(from = 0, to = 65535) int messageRef,
+            @StatusReportResult int result) {
         Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented.");
     }
 
@@ -210,12 +213,12 @@
      * {@link #DELIVER_STATUS_ERROR_GENERIC} result code.
      * @param token unique token generated by IMS providers that the platform will use to trigger
      *              callbacks for this message.
-     * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
-     * {@link SmsMessage#FORMAT_3GPP2}.
+     * @param format the format of the message.
      * @param pdu PDU representing the contents of the message.
      * @throws RuntimeException if called before {@link #onReady()} is triggered.
      */
-    public final void onSmsReceived(int token, String format, byte[] pdu) throws RuntimeException {
+    public final void onSmsReceived(int token, @SmsMessage.Format String format, byte[] pdu)
+            throws RuntimeException {
         synchronized (mLock) {
             if (mListener == null) {
                 throw new RuntimeException("Feature not ready.");
@@ -241,13 +244,16 @@
      * sent successfully.
      *
      * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
-     * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+     * @param messageRef the message reference, which may be 1 byte if it is in
+     *     {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+     *     {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
      *
      * @throws RuntimeException if called before {@link #onReady()} is triggered or if the
      * connection to the framework is not available. If this happens attempting to send the SMS
      * should be aborted.
      */
-    public final void onSendSmsResultSuccess(int token, int messageRef) throws RuntimeException {
+    public final void onSendSmsResultSuccess(int token,
+            @IntRange(from = 0, to = 65535) int messageRef) throws RuntimeException {
         synchronized (mLock) {
             if (mListener == null) {
                 throw new RuntimeException("Feature not ready.");
@@ -266,34 +272,11 @@
      * to the platform.
      *
      * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
-     * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+     * @param messageRef the message reference, which may be 1 byte if it is in
+     *     {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+     *     {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
      * @param status result of sending the SMS.
-     * @param reason reason in case status is failure. Valid values are:
-     *  {@link SmsManager#RESULT_ERROR_NONE},
-     *  {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
-     *  {@link SmsManager#RESULT_ERROR_RADIO_OFF},
-     *  {@link SmsManager#RESULT_ERROR_NULL_PDU},
-     *  {@link SmsManager#RESULT_ERROR_NO_SERVICE},
-     *  {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
-     *  {@link SmsManager#RESULT_ERROR_FDN_CHECK_FAILURE},
-     *  {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
-     *  {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED},
-     *  {@link SmsManager#RESULT_RADIO_NOT_AVAILABLE},
-     *  {@link SmsManager#RESULT_NETWORK_REJECT},
-     *  {@link SmsManager#RESULT_INVALID_ARGUMENTS},
-     *  {@link SmsManager#RESULT_INVALID_STATE},
-     *  {@link SmsManager#RESULT_NO_MEMORY},
-     *  {@link SmsManager#RESULT_INVALID_SMS_FORMAT},
-     *  {@link SmsManager#RESULT_SYSTEM_ERROR},
-     *  {@link SmsManager#RESULT_MODEM_ERROR},
-     *  {@link SmsManager#RESULT_NETWORK_ERROR},
-     *  {@link SmsManager#RESULT_ENCODING_ERROR},
-     *  {@link SmsManager#RESULT_INVALID_SMSC_ADDRESS},
-     *  {@link SmsManager#RESULT_OPERATION_NOT_ALLOWED},
-     *  {@link SmsManager#RESULT_INTERNAL_ERROR},
-     *  {@link SmsManager#RESULT_NO_RESOURCES},
-     *  {@link SmsManager#RESULT_CANCELLED},
-     *  {@link SmsManager#RESULT_REQUEST_NOT_SUPPORTED}
+     * @param reason reason in case status is failure.
      *
      * @throws RuntimeException if called before {@link #onReady()} is triggered or if the
      * connection to the framework is not available. If this happens attempting to send the SMS
@@ -303,8 +286,8 @@
      * send result.
      */
     @Deprecated
-    public final void onSendSmsResult(int token, int messageRef,  @SendStatusResult int status,
-            int reason) throws RuntimeException {
+    public final void onSendSmsResult(int token, @IntRange(from = 0, to = 65535) int messageRef,
+            @SendStatusResult int status, @SmsManager.Result int reason) throws RuntimeException {
         synchronized (mLock) {
             if (mListener == null) {
                 throw new RuntimeException("Feature not ready.");
@@ -324,34 +307,10 @@
      * network.
      *
      * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
-     * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+     * @param messageRef the message reference, which may be 1 byte if it is in
+     *     {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+     *     {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
      * @param status result of sending the SMS.
-     * @param reason Valid values are:
-     *  {@link SmsManager#RESULT_ERROR_NONE},
-     *  {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
-     *  {@link SmsManager#RESULT_ERROR_RADIO_OFF},
-     *  {@link SmsManager#RESULT_ERROR_NULL_PDU},
-     *  {@link SmsManager#RESULT_ERROR_NO_SERVICE},
-     *  {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
-     *  {@link SmsManager#RESULT_ERROR_FDN_CHECK_FAILURE},
-     *  {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
-     *  {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED},
-     *  {@link SmsManager#RESULT_RADIO_NOT_AVAILABLE},
-     *  {@link SmsManager#RESULT_NETWORK_REJECT},
-     *  {@link SmsManager#RESULT_INVALID_ARGUMENTS},
-     *  {@link SmsManager#RESULT_INVALID_STATE},
-     *  {@link SmsManager#RESULT_NO_MEMORY},
-     *  {@link SmsManager#RESULT_INVALID_SMS_FORMAT},
-     *  {@link SmsManager#RESULT_SYSTEM_ERROR},
-     *  {@link SmsManager#RESULT_MODEM_ERROR},
-     *  {@link SmsManager#RESULT_NETWORK_ERROR},
-     *  {@link SmsManager#RESULT_ENCODING_ERROR},
-     *  {@link SmsManager#RESULT_INVALID_SMSC_ADDRESS},
-     *  {@link SmsManager#RESULT_OPERATION_NOT_ALLOWED},
-     *  {@link SmsManager#RESULT_INTERNAL_ERROR},
-     *  {@link SmsManager#RESULT_NO_RESOURCES},
-     *  {@link SmsManager#RESULT_CANCELLED},
-     *  {@link SmsManager#RESULT_REQUEST_NOT_SUPPORTED}
      * @param networkErrorCode the error code reported by the carrier network if sending this SMS
      *  has resulted in an error or {@link #RESULT_NO_NETWORK_ERROR} if no network error was
      *  generated. See 3GPP TS 24.011 Section 7.3.4 for valid error codes and more information.
@@ -360,9 +319,9 @@
      * connection to the framework is not available. If this happens attempting to send the SMS
      * should be aborted.
      */
-    public final void onSendSmsResultError(int token, int messageRef,  @SendStatusResult int status,
-            int reason, int networkErrorCode)
-            throws RuntimeException {
+    public final void onSendSmsResultError(int token,
+            @IntRange(from = 0, to = 65535) int messageRef, @SendStatusResult int status,
+            @SmsManager.Result int reason, int networkErrorCode) throws RuntimeException {
         synchronized (mLock) {
             if (mListener == null) {
                 throw new RuntimeException("Feature not ready.");
@@ -384,9 +343,10 @@
      * the platform is not available, {@link #acknowledgeSmsReport(int, int, int)} will be called
      * with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
      * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
-     * @param messageRef the message reference.
-     * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
-     *               {@link SmsMessage#FORMAT_3GPP2}.
+     * @param messageRef the message reference, which may be 1 byte if it is in
+     *     {@link SmsMessage#FORMAT_3GPP} format or 2 bytes if it is in
+     *     {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+     * @param format the format of the message.
      * @param pdu PDU representing the content of the status report.
      * @throws RuntimeException if called before {@link #onReady()} is triggered
      *
@@ -394,7 +354,8 @@
      * message reference.
      */
     @Deprecated
-    public final void onSmsStatusReportReceived(int token, int messageRef, String format,
+    public final void onSmsStatusReportReceived(int token,
+            @IntRange(from = 0, to = 65535) int messageRef, @SmsMessage.Format String format,
             byte[] pdu) throws RuntimeException {
         synchronized (mLock) {
             if (mListener == null) {
@@ -419,13 +380,12 @@
      * with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
      * @param token unique token generated by IMS providers that the platform will use to trigger
      *              callbacks for this message.
-     * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
-     *               {@link SmsMessage#FORMAT_3GPP2}.
+     * @param format the format of the message.
      * @param pdu PDU representing the content of the status report.
      * @throws RuntimeException if called before {@link #onReady()} is triggered
      */
-    public final void onSmsStatusReportReceived(int token, String format, byte[] pdu)
-            throws RuntimeException {
+    public final void onSmsStatusReportReceived(int token, @SmsMessage.Format String format,
+            byte[] pdu) throws RuntimeException {
         synchronized (mLock) {
             if (mListener == null) {
                 throw new RuntimeException("Feature not ready.");
@@ -450,13 +410,11 @@
     }
 
     /**
-     * Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS
-     * Provider.
+     * Returns the SMS format that the ImsService expects.
      *
-     * @return  the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
-     * {@link SmsMessage#FORMAT_3GPP2}.
+     * @return  The expected format of the SMS messages.
      */
-    public String getSmsFormat() {
+    public @SmsMessage.Format String getSmsFormat() {
       return SmsMessage.FORMAT_3GPP;
     }
 
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 668a6af..9e786ce 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -111,7 +111,7 @@
     public static final int EVENT_DATA_SERVICE_BINDING_CHANGED = BASE + 49;
     public static final int EVENT_DEVICE_PROVISIONED_CHANGE = BASE + 50;
     public static final int EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED = BASE + 51;
-    public static final int EVENT_5G_NETWORK_CHANGED = BASE + 52;
+    public static final int EVENT_SERVICE_STATE_CHANGED = BASE + 52;
     public static final int EVENT_5G_TIMER_HYSTERESIS = BASE + 53;
     public static final int EVENT_5G_TIMER_WATCHDOG = BASE + 54;
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index fd7ec56..39e00cc 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2063,4 +2063,9 @@
      * data might be disabled on non-default data subscription but explicitly turned on by settings.
      */
     boolean isDataAllowedInVoiceCall(int subId);
+
+    /**
+     * Command line command to enable or disable handling of CEP data for test purposes.
+     */
+    oneway void setCepEnabled(boolean isCepEnabled);
 }
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 4654437..b357fa4 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -900,6 +900,20 @@
     }
 
     /**
+     * @return the bearer data byte array
+     */
+    public byte[] getEnvelopeBearerData() {
+        return mEnvelope.bearerData;
+    }
+
+    /**
+     * @return the 16-bit CDMA SCPT service category
+     */
+    public @CdmaSmsCbProgramData.Category int getEnvelopeServiceCategory() {
+        return mEnvelope.serviceCategory;
+    }
+
+    /**
      * {@inheritDoc}
      */
     @Override
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
index 2787b24..c924ab3 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
@@ -57,17 +57,17 @@
 
     // CMAS alert service category assignments, see 3GPP2 C.R1001 table 9.3.3-1
     public static final int SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT  =
-            CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT;
+            CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT;  // = 4096
     public static final int SERVICE_CATEGORY_CMAS_EXTREME_THREAT            =
-            CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT;
+            CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT;            // = 4097
     public static final int SERVICE_CATEGORY_CMAS_SEVERE_THREAT             =
-            CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT;
+            CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT;             // = 4098
     public static final int SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY =
-            CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY;
+            CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY; // = 4099
     public static final int SERVICE_CATEGORY_CMAS_TEST_MESSAGE              =
-            CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE;
+            CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE;              // = 4100
     public static final int SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE       =
-            CdmaSmsCbProgramData.CATEGORY_CMAS_LAST_RESERVED_VALUE;
+            CdmaSmsCbProgramData.CATEGORY_CMAS_LAST_RESERVED_VALUE;       // = 4351
 
     /**
      * Provides the type of a SMS message like point to point, broadcast or acknowledge
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index fcd4701..5053cee 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -758,7 +758,7 @@
 
     /** {@hide} */
     @Override
-    public Context createContextAsUser(UserHandle user) {
+    public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) {
         throw new UnsupportedOperationException();
     }
 
diff --git a/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp b/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
index b0c7251..02dfd73 100644
--- a/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
+++ b/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
@@ -42,6 +42,42 @@
 //  https://www.kernel.org/doc/Documentation/filesystems/fiemap.txt
 //  https://git.kernel.org/pub/scm/fs/xfs/xfsprogs-dev.git/tree/io/fiemap.c
 
+#ifndef F2FS_IOC_SET_PIN_FILE
+#ifndef F2FS_IOCTL_MAGIC
+#define F2FS_IOCTL_MAGIC 0xf5
+#endif
+#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
+#define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32)
+#endif
+
+struct Args {
+  const char* block_device;
+  const char* file_name;
+  uint64_t byte_offset;
+  bool use_f2fs_pinning;
+};
+
+class ScopedF2fsFilePinning {
+ public:
+  explicit ScopedF2fsFilePinning(const char* file_path) {
+    fd_.reset(TEMP_FAILURE_RETRY(open(file_path, O_WRONLY | O_CLOEXEC, 0)));
+    if (fd_.get() == -1) {
+      perror("Failed to open");
+      return;
+    }
+    __u32 set = 1;
+    ioctl(fd_.get(), F2FS_IOC_SET_PIN_FILE, &set);
+  }
+
+  ~ScopedF2fsFilePinning() {
+    __u32 set = 0;
+    ioctl(fd_.get(), F2FS_IOC_SET_PIN_FILE, &set);
+  }
+
+ private:
+  android::base::unique_fd fd_;
+};
+
 ssize_t get_logical_block_size(const char* block_device) {
   android::base::unique_fd fd(open(block_device, O_RDONLY));
   if (fd.get() < 0) {
@@ -138,28 +174,51 @@
   return 0;
 }
 
-int main(int argc, const char** argv) {
-  if (argc != 4) {
+std::unique_ptr<Args> parse_args(int argc, const char** argv) {
+  if (argc != 4 && argc != 5) {
     fprintf(stderr,
-            "Usage: %s block_dev filename byte_offset\n"
+            "Usage: %s [--use-f2fs-pinning] block_dev filename byte_offset\n"
             "\n"
             "This program bypasses filesystem and damages the specified byte\n"
             "at the physical position on <block_dev> corresponding to the\n"
             "logical byte location in <filename>.\n",
             argv[0]);
+    return nullptr;
+  }
+
+  auto args = std::make_unique<Args>();
+  const char** arg = &argv[1];
+  args->use_f2fs_pinning = strcmp(*arg, "--use-f2fs-pinning") == 0;
+  if (args->use_f2fs_pinning) {
+    ++arg;
+  }
+  args->block_device = *(arg++);
+  args->file_name = *(arg++);
+  args->byte_offset = strtoull(*arg, nullptr, 10);
+  if (args->byte_offset == ULLONG_MAX) {
+    perror("Invalid byte offset");
+    return nullptr;
+  }
+  return args;
+}
+
+int main(int argc, const char** argv) {
+  std::unique_ptr<Args> args = parse_args(argc, argv);
+  if (args == nullptr) {
     return -1;
   }
 
-  const char* block_device = argv[1];
-  const char* file_name = argv[2];
-  uint64_t byte_offset = strtoull(argv[3], nullptr, 10);
-
-  ssize_t block_size = get_logical_block_size(block_device);
+  ssize_t block_size = get_logical_block_size(args->block_device);
   if (block_size < 0) {
     return -1;
   }
 
-  int64_t physical_offset_signed = get_physical_offset(file_name, byte_offset);
+  std::unique_ptr<ScopedF2fsFilePinning> pinned_file;
+  if (args->use_f2fs_pinning) {
+    pinned_file = std::make_unique<ScopedF2fsFilePinning>(args->file_name);
+  }
+
+  int64_t physical_offset_signed = get_physical_offset(args->file_name, args->byte_offset);
   if (physical_offset_signed < 0) {
     return -1;
   }
@@ -172,7 +231,7 @@
   std::unique_ptr<char> buf(static_cast<char*>(
       aligned_alloc(block_size /* alignment */, block_size /* size */)));
 
-  if (read_block_from_device(block_device, physical_block_offset, block_size,
+  if (read_block_from_device(args->block_device, physical_block_offset, block_size,
                              buf.get()) < 0) {
     return -1;
   }
@@ -180,7 +239,7 @@
   printf("before: %hhx\n", *p);
   *p ^= 0xff;
   printf("after: %hhx\n", *p);
-  if (write_block_to_device(block_device, physical_block_offset, block_size,
+  if (write_block_to_device(args->block_device, physical_block_offset, block_size,
                             buf.get()) < 0) {
     return -1;
   }
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index 761c5ce..2445a6a 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -38,6 +38,7 @@
 import org.junit.runner.RunWith;
 
 import java.io.FileNotFoundException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 
@@ -440,8 +441,15 @@
             throws DeviceNotAvailableException {
         assertTrue(path.startsWith("/data/"));
         ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data");
-        expectRemoteCommandToSucceed(String.join(" ", DAMAGING_EXECUTABLE,
-                    mountPoint.filesystem, path, Long.toString(offsetOfTargetingByte)));
+        ArrayList<String> args = new ArrayList<>();
+        args.add(DAMAGING_EXECUTABLE);
+        if ("f2fs".equals(mountPoint.type)) {
+            args.add("--use-f2fs-pinning");
+        }
+        args.add(mountPoint.filesystem);
+        args.add(path);
+        args.add(Long.toString(offsetOfTargetingByte));
+        expectRemoteCommandToSucceed(String.join(" ", args));
     }
 
     private String getApkPath(String packageName) throws DeviceNotAvailableException {
diff --git a/tests/Codegen/runTest.sh b/tests/Codegen/runTest.sh
index 0e90dea..929f122 100755
--- a/tests/Codegen/runTest.sh
+++ b/tests/Codegen/runTest.sh
@@ -17,11 +17,13 @@
         header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java && \
         header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java && \
         header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java && \
-        cd $ANDROID_BUILD_TOP &&
-        header_and_eval mmma -j16 frameworks/base/tests/Codegen && \
-        header_and_eval adb install -r -t "$(find $ANDROID_TARGET_OUT_TESTCASES -name 'CodegenTests.apk')" && \
-        # header_and_eval adb shell am set-debug-app -w com.android.codegentest && \
-        header_and_eval adb shell am instrument -w -e package com.android.codegentest com.android.codegentest/androidx.test.runner.AndroidJUnitRunner
+        (
+            cd $ANDROID_BUILD_TOP &&
+            header_and_eval mmma -j16 frameworks/base/tests/Codegen && \
+            header_and_eval adb install -r -t "$(find $ANDROID_TARGET_OUT_TESTCASES -name 'CodegenTests.apk')" && \
+            # header_and_eval adb shell am set-debug-app -w com.android.codegentest && \
+            header_and_eval adb shell am instrument -w -e package com.android.codegentest com.android.codegentest/androidx.test.runner.AndroidJUnitRunner
+        )
 
         exitCode=$?
 
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
index 10eba6a..3515053 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
@@ -32,13 +32,17 @@
 
 
 
-    // Code below generated by codegen v1.0.7.
+    // Code below generated by codegen v1.0.8.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
     // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
 
 
     @DataClass.Generated.Member
@@ -94,8 +98,8 @@
     };
 
     @DataClass.Generated(
-            time = 1570576455287L,
-            codegenVersion = "1.0.7",
+            time = 1570828332402L,
+            codegenVersion = "1.0.8",
             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 1085a6a..c867409 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
@@ -46,13 +46,17 @@
 
 
 
-    // Code below generated by codegen v1.0.7.
+    // Code below generated by codegen v1.0.8.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
     // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
 
 
     @DataClass.Generated.Member
@@ -116,8 +120,8 @@
     };
 
     @DataClass.Generated(
-            time = 1570576456245L,
-            codegenVersion = "1.0.7",
+            time = 1570828333399L,
+            codegenVersion = "1.0.8",
             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 75ef963..8d097a0 100644
--- a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
@@ -16,6 +16,7 @@
 package com.android.codegentest;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.SparseArray;
@@ -46,15 +47,22 @@
     @NonNull SparseArray<SampleWithCustomBuilder> mSparseArray = null;
     @NonNull SparseIntArray mSparseIntArray = null;
 
+    @SuppressWarnings({"WeakerAccess"})
+    @Nullable Boolean mNullableBoolean = null;
 
 
-    // Code below generated by codegen v1.0.7.
+
+    // Code below generated by codegen v1.0.8.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
     // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
 
 
     @DataClass.Generated.Member
@@ -65,7 +73,8 @@
             @NonNull Map<String,SampleWithCustomBuilder> map,
             @NonNull Map<String,String> stringMap,
             @NonNull SparseArray<SampleWithCustomBuilder> sparseArray,
-            @NonNull SparseIntArray sparseIntArray) {
+            @NonNull SparseIntArray sparseIntArray,
+            @SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean nullableBoolean) {
         this.mStringArray = stringArray;
         AnnotationValidations.validate(
                 NonNull.class, null, mStringArray);
@@ -87,6 +96,7 @@
         this.mSparseIntArray = sparseIntArray;
         AnnotationValidations.validate(
                 NonNull.class, null, mSparseIntArray);
+        this.mNullableBoolean = nullableBoolean;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -126,6 +136,11 @@
         return mSparseIntArray;
     }
 
+    @DataClass.Generated.Member
+    public @SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean getNullableBoolean() {
+        return mNullableBoolean;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -139,7 +154,8 @@
                 "map = " + mMap + ", " +
                 "stringMap = " + mStringMap + ", " +
                 "sparseArray = " + mSparseArray + ", " +
-                "sparseIntArray = " + mSparseIntArray +
+                "sparseIntArray = " + mSparseIntArray + ", " +
+                "nullableBoolean = " + mNullableBoolean +
         " }";
     }
 
@@ -149,6 +165,9 @@
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
+        int flg = 0;
+        if (mNullableBoolean != null) flg |= 0x80;
+        dest.writeInt(flg);
         dest.writeStringArray(mStringArray);
         dest.writeIntArray(mIntArray);
         dest.writeStringList(mStringList);
@@ -156,6 +175,7 @@
         dest.writeMap(mStringMap);
         dest.writeSparseArray(mSparseArray);
         dest.writeSparseIntArray(mSparseIntArray);
+        if (mNullableBoolean != null) dest.writeBoolean(mNullableBoolean);
     }
 
     @Override
@@ -169,6 +189,7 @@
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
+        int flg = in.readInt();
         String[] stringArray = in.createStringArray();
         int[] intArray = in.createIntArray();
         List<String> stringList = new java.util.ArrayList<>();
@@ -179,6 +200,7 @@
         in.readMap(stringMap, String.class.getClassLoader());
         SparseArray<SampleWithCustomBuilder> sparseArray = (SparseArray) in.readSparseArray(SampleWithCustomBuilder.class.getClassLoader());
         SparseIntArray sparseIntArray = (SparseIntArray) in.readSparseIntArray();
+        Boolean nullableBoolean = (flg & 0x80) == 0 ? null : (Boolean) in.readBoolean();
 
         this.mStringArray = stringArray;
         AnnotationValidations.validate(
@@ -201,6 +223,7 @@
         this.mSparseIntArray = sparseIntArray;
         AnnotationValidations.validate(
                 NonNull.class, null, mSparseIntArray);
+        this.mNullableBoolean = nullableBoolean;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -233,6 +256,7 @@
         private @NonNull Map<String,String> mStringMap;
         private @NonNull SparseArray<SampleWithCustomBuilder> mSparseArray;
         private @NonNull SparseIntArray mSparseIntArray;
+        private @SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean mNullableBoolean;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -328,10 +352,18 @@
             return this;
         }
 
+        @DataClass.Generated.Member
+        public @NonNull Builder setNullableBoolean(@SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x80;
+            mNullableBoolean = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public ParcelAllTheThingsDataClass build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x80; // Mark builder used
+            mBuilderFieldsSet |= 0x100; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mStringArray = null;
@@ -354,6 +386,9 @@
             if ((mBuilderFieldsSet & 0x40) == 0) {
                 mSparseIntArray = null;
             }
+            if ((mBuilderFieldsSet & 0x80) == 0) {
+                mNullableBoolean = null;
+            }
             ParcelAllTheThingsDataClass o = new ParcelAllTheThingsDataClass(
                     mStringArray,
                     mIntArray,
@@ -361,12 +396,13 @@
                     mMap,
                     mStringMap,
                     mSparseArray,
-                    mSparseIntArray);
+                    mSparseIntArray,
+                    mNullableBoolean);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x80) != 0) {
+            if ((mBuilderFieldsSet & 0x100) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -374,10 +410,10 @@
     }
 
     @DataClass.Generated(
-            time = 1570576454326L,
-            codegenVersion = "1.0.7",
+            time = 1570828331396L,
+            codegenVersion = "1.0.8",
             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\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)")
+            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
     private void __metadata() {}
 
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index 14010a9..d014d6d 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -342,13 +342,17 @@
 
 
 
-    // Code below generated by codegen v1.0.7.
+    // Code below generated by codegen v1.0.8.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
     // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
 
 
     @IntDef(prefix = "STATE_", value = {
@@ -1868,8 +1872,8 @@
     }
 
     @DataClass.Generated(
-            time = 1570576452225L,
-            codegenVersion = "1.0.7",
+            time = 1570828329319L,
+            codegenVersion = "1.0.8",
             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 b5f6c73..1c87e8f 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
@@ -85,13 +85,17 @@
 
 
 
-    // Code below generated by codegen v1.0.7.
+    // Code below generated by codegen v1.0.8.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
     // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
 
 
     @DataClass.Generated.Member
@@ -249,8 +253,8 @@
     }
 
     @DataClass.Generated(
-            time = 1570576453295L,
-            codegenVersion = "1.0.7",
+            time = 1570828330331L,
+            codegenVersion = "1.0.8",
             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 0ce8aba..27af37f 100644
--- a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
+++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
@@ -51,18 +51,22 @@
 
 
 
-    // Code below generated by codegen v1.0.7.
+    // Code below generated by codegen v1.0.8.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
     // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
 
 
     @DataClass.Generated(
-            time = 1570576457249L,
-            codegenVersion = "1.0.7",
+            time = 1570828334384L,
+            codegenVersion = "1.0.8",
             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/tests/FlickerTests/TEST_MAPPING b/tests/FlickerTests/TEST_MAPPING
index 55a6147..f34c432 100644
--- a/tests/FlickerTests/TEST_MAPPING
+++ b/tests/FlickerTests/TEST_MAPPING
@@ -1,7 +1,8 @@
 {
   "postsubmit": [
     {
-      "name": "FlickerTests"
+      "name": "FlickerTests",
+      "keywords": ["primary-device"]
     }
   ]
 }
\ No newline at end of file
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 231d045b..085c53c 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -19,7 +19,6 @@
     static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
     test_suites: ["general-tests"],
     test_config: "RollbackTest.xml",
-    // TODO: sdk_version: "test_current" when Intent#resolveSystemservice is TestApi
 }
 
 java_test_host {
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 9e6ac8e..8b97f61 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -92,7 +92,7 @@
      * Enable rollback phase.
      */
     @Test
-    public void testBadApkOnlyEnableRollback() throws Exception {
+    public void testBadApkOnly_Phase1() throws Exception {
         Uninstall.packages(TestApp.A);
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
 
@@ -101,9 +101,6 @@
         InstallUtils.processUserData(TestApp.A);
 
         Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit();
-
-        // At this point, the host test driver will reboot the device and run
-        // testBadApkOnlyConfirmEnableRollback().
     }
 
     /**
@@ -111,7 +108,7 @@
      * Confirm that rollback was successfully enabled.
      */
     @Test
-    public void testBadApkOnlyConfirmEnableRollback() throws Exception {
+    public void testBadApkOnly_Phase2() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
         InstallUtils.processUserData(TestApp.A);
 
@@ -122,9 +119,6 @@
         assertThat(rollback).packagesContainsExactly(
                 Rollback.from(TestApp.A2).to(TestApp.A1));
         assertThat(rollback.isStaged()).isTrue();
-
-        // At this point, the host test driver will run
-        // testBadApkOnlyTriggerRollback().
     }
 
     /**
@@ -133,15 +127,14 @@
      * rebooting the test out from under it.
      */
     @Test
-    public void testBadApkOnlyTriggerRollback() throws Exception {
+    public void testBadApkOnly_Phase3() throws Exception {
         // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
         RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
 
-        // We expect the device to be rebooted automatically. Wait for that to
-        // happen. At that point, the host test driver will wait for the
-        // device to come back up and run testApkOnlyConfirmRollback().
+        // We expect the device to be rebooted automatically. Wait for that to happen.
         Thread.sleep(30 * 1000);
 
+        // Raise an error anyway if reboot didn't happen.
         fail("watchdog did not trigger reboot");
     }
 
@@ -150,7 +143,7 @@
      * Confirm rollback phase.
      */
     @Test
-    public void testBadApkOnlyConfirmRollback() throws Exception {
+    public void testBadApkOnly_Phase4() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         InstallUtils.processUserData(TestApp.A);
 
@@ -177,8 +170,11 @@
                         networkStack)).isNull();
     }
 
+    /**
+     * Stage install ModuleMetadata package to simulate a Mainline module update.
+     */
     @Test
-    public void installModuleMetadataPackage() throws Exception {
+    public void testNativeWatchdogTriggersRollback_Phase1() throws Exception {
         resetModuleMetadataPackage();
         Context context = InstrumentationRegistry.getContext();
         PackageInfo metadataPackageInfo = context.getPackageManager().getPackageInfo(
@@ -192,6 +188,26 @@
                 + metadataApkPath);
     }
 
+    /**
+     * Verify the rollback is available.
+     */
+    @Test
+    public void testNativeWatchdogTriggersRollback_Phase2() throws Exception {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+                        MODULE_META_DATA_PACKAGE)).isNotNull();
+    }
+
+    /**
+     * Verify the rollback is committed after crashing.
+     */
+    @Test
+    public void testNativeWatchdogTriggersRollback_Phase3() throws Exception {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+                        MODULE_META_DATA_PACKAGE)).isNotNull();
+    }
+
     @Test
     public void assertNetworkStackRollbackAvailable() throws Exception {
         RollbackManager rm = RollbackUtils.getRollbackManager();
@@ -213,20 +229,6 @@
                         getNetworkStackPackageName())).isNull();
     }
 
-    @Test
-    public void assertModuleMetadataRollbackAvailable() throws Exception {
-        RollbackManager rm = RollbackUtils.getRollbackManager();
-        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
-                        MODULE_META_DATA_PACKAGE)).isNotNull();
-    }
-
-    @Test
-    public void assertModuleMetadataRollbackCommitted() throws Exception {
-        RollbackManager rm = RollbackUtils.getRollbackManager();
-        assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
-                        MODULE_META_DATA_PACKAGE)).isNotNull();
-    }
-
     private String getNetworkStackPackageName() {
         Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS);
         ComponentName comp = intent.resolveSystemService(
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index bc98f06..2043027 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -68,35 +68,35 @@
      */
     @Test
     public void testBadApkOnly() throws Exception {
-        runPhase("testBadApkOnlyEnableRollback");
+        runPhase("testBadApkOnly_Phase1");
         getDevice().reboot();
-        runPhase("testBadApkOnlyConfirmEnableRollback");
+        runPhase("testBadApkOnly_Phase2");
         try {
             // This is expected to fail due to the device being rebooted out
             // from underneath the test. If this fails for reasons other than
             // the device reboot, those failures should result in failure of
             // the testApkOnlyConfirmRollback phase.
             CLog.logAndDisplay(LogLevel.INFO, "testBadApkOnlyTriggerRollback is expected to fail");
-            runPhase("testBadApkOnlyTriggerRollback");
+            runPhase("testBadApkOnly_Phase3");
         } catch (AssertionError e) {
             // AssertionError is expected.
         }
 
         getDevice().waitForDeviceAvailable();
 
-        runPhase("testBadApkOnlyConfirmRollback");
+        runPhase("testBadApkOnly_Phase4");
     }
 
     @Test
     public void testNativeWatchdogTriggersRollback() throws Exception {
         //Stage install ModuleMetadata package - this simulates a Mainline module update
-        runPhase("installModuleMetadataPackage");
+        runPhase("testNativeWatchdogTriggersRollback_Phase1");
 
         // Reboot device to activate staged package
         getDevice().reboot();
         getDevice().waitForDeviceAvailable();
 
-        runPhase("assertModuleMetadataRollbackAvailable");
+        runPhase("testNativeWatchdogTriggersRollback_Phase2");
 
         // crash system_server enough times to trigger a rollback
         crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
@@ -113,7 +113,7 @@
         getDevice().waitForDeviceAvailable();
 
         // verify rollback committed
-        runPhase("assertModuleMetadataRollbackCommitted");
+        runPhase("testNativeWatchdogTriggersRollback_Phase3");
     }
 
     /**
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index bffbbfd..cf3fba8 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -5709,7 +5709,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 140305678)
     public void testTcpBufferReset() throws Exception {
         final String testTcpBufferSizes = "1,2,3,4,5,6";
         final NetworkRequest networkRequest = new NetworkRequest.Builder()
diff --git a/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt
index ba00264..1a7fd6e 100644
--- a/tools/codegen/src/com/android/codegen/FieldInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt
@@ -191,7 +191,7 @@
      * Parcel.write* and Parcel.read* method name wildcard values
      */
     val ParcelMethodsSuffix = when {
-        FieldClass in PRIMITIVE_TYPES - "char" - "boolean" +
+        FieldClass in PRIMITIVE_TYPES - "char" - "boolean" + BOXED_PRIMITIVE_TYPES +
                 listOf("String", "CharSequence", "Exception", "Size", "SizeF", "Bundle",
                         "FileDescriptor", "SparseBooleanArray", "SparseIntArray", "SparseArray") ->
             FieldClass
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
index 5a95676..0ebb3cf 100644
--- a/tools/codegen/src/com/android/codegen/Generators.kt
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -854,6 +854,7 @@
         it.nameAsString == intOrStringDef?.AnnotationName
                 || it.nameAsString in knownNonValidationAnnotations
                 || it in perElementValidations
+                || it.args.any { (_, value) -> value is ArrayInitializerExpr }
     }.forEach { annotation ->
         appendValidateCall(annotation,
                 valueToValidate = if (annotation.nameAsString == Size) sizeExpr else name)
@@ -874,14 +875,7 @@
     val validate = memberRef("com.android.internal.util.AnnotationValidations.validate")
     "$validate(" {
         !"${annotation.nameAsString}.class, null, $valueToValidate"
-        val params = when (annotation) {
-            is MarkerAnnotationExpr -> emptyMap()
-            is SingleMemberAnnotationExpr -> mapOf("value" to annotation.memberValue)
-            is NormalAnnotationExpr ->
-                annotation.pairs.map { it.name.asString() to it.value }.toMap()
-            else -> throw IllegalStateException()
-        }
-        params.forEach { name, value ->
+        annotation.args.forEach { name, value ->
             !",\n\"$name\", $value"
         }
     }
diff --git a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
index 24cf469..d6953c0 100644
--- a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
+++ b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
@@ -87,6 +87,14 @@
         is IntegerLiteralExpr -> sb.append(ex.asInt()).append("L")
         is LongLiteralExpr -> sb.append(ex.asLong()).append("L")
         is DoubleLiteralExpr -> sb.append(ex.asDouble())
+        is ArrayInitializerExpr -> {
+            sb.append("{")
+            ex.values.forEachLastAware { arrayElem, isLast ->
+                appendExpr(sb, arrayElem)
+                if (!isLast) sb.append(", ")
+            }
+            sb.append("}")
+        }
         else -> sb.append(ex)
     }
 }
diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt
index 039f7b2..ce83d3d 100755
--- a/tools/codegen/src/com/android/codegen/Main.kt
+++ b/tools/codegen/src/com/android/codegen/Main.kt
@@ -9,6 +9,7 @@
 const val INDENT_SINGLE = "    "
 
 val PRIMITIVE_TYPES = listOf("byte", "short", "int", "long", "char", "float", "double", "boolean")
+val BOXED_PRIMITIVE_TYPES = PRIMITIVE_TYPES.map { it.capitalize() } - "Int" + "Integer" - "Char" + "Character"
 
 val BUILTIN_SPECIAL_PARCELLINGS = listOf("Pattern")
 
@@ -142,6 +143,10 @@
         //
         // To regenerate run:
         // $ $cliExecutable ${cliArgs.dropLast(1).joinToString("") { "$it " }}$fileEscaped
+        //
+        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+        //   Settings > Editor > Code Style > Formatter Control
+        //@formatter:off
 
         """
 
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index 47f774f..8c4583f 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.7"
+const val CODEGEN_VERSION = "1.0.8"
 
 const val CANONICAL_BUILDER_CLASS = "Builder"
 const val BASE_BUILDER_CLASS = "BaseBuilder"
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
index a1f068a..e703397 100644
--- a/tools/codegen/src/com/android/codegen/Utils.kt
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -1,9 +1,6 @@
 package com.android.codegen
 
-import com.github.javaparser.ast.Modifier
-import com.github.javaparser.ast.expr.AnnotationExpr
-import com.github.javaparser.ast.expr.Expression
-import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr
+import com.github.javaparser.ast.expr.*
 import com.github.javaparser.ast.nodeTypes.NodeWithModifiers
 import java.time.Instant
 import java.time.ZoneId
@@ -88,3 +85,10 @@
 }
 
 fun bitAtExpr(bitIndex: Int) = "0x${java.lang.Long.toHexString(1L shl bitIndex)}"
+
+val AnnotationExpr.args: Map<String, Expression> get() = when (this) {
+    is MarkerAnnotationExpr -> emptyMap()
+    is SingleMemberAnnotationExpr -> mapOf("value" to memberValue)
+    is NormalAnnotationExpr -> pairs.map { it.name.asString() to it.value }.toMap()
+    else -> throw IllegalArgumentException("Unknown annotation expression: $this")
+}
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 14eead8..1390f63 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -29,7 +29,7 @@
         "-Werror",
     ],
 
-    shared_libs: ["libprotoc"],
+    static_libs: ["libprotoc"],
 }
 
 cc_binary_host {