Merge "[Magnifier - 14] Follow finger instead of cursor"
diff --git a/Android.bp b/Android.bp
index 82a972a..2685ac3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -471,8 +471,6 @@
         "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl",
         "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl",
         "telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl",
-        "telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl",
-        "telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl",
         "telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl",
         "telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl",
 	"telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl",
@@ -492,6 +490,8 @@
         "telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl",
         "telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl",
         "telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl",
+        "telephony/java/com/android/ims/internal/IImsRegistration.aidl",
+        "telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl",
         "telephony/java/com/android/ims/internal/IImsRcsFeature.aidl",
         "telephony/java/com/android/ims/internal/IImsService.aidl",
         "telephony/java/com/android/ims/internal/IImsServiceController.aidl",
@@ -519,7 +519,9 @@
         "telephony/java/com/android/internal/telephony/ITelephony.aidl",
         "telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl",
         "telephony/java/com/android/internal/telephony/IWapPushManager.aidl",
+        "telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl",
         "telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl",
+        "telephony/java/com/android/internal/telephony/euicc/IGetAllProfilesCallback.aidl",
         "wifi/java/android/net/wifi/IWifiManager.aidl",
         "wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl",
         "wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl",
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index 4889941..bd0b944 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -2,6 +2,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.perftests.core">
 
+    <permission android:name="com.android.perftests.core.TestPermission" />
+    <uses-permission android:name="com.android.perftests.core.TestPermission" />
+
     <uses-permission
         android:name="android.permission.GET_ACCOUNTS" />
 
diff --git a/apct-tests/perftests/core/src/android/os/PermissionTest.java b/apct-tests/perftests/core/src/android/os/PermissionTest.java
new file mode 100644
index 0000000..d292e7d
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/PermissionTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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 android.os;
+
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.content.Context;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class PermissionTest {
+    private static final String PERMISSION_HAS_NAME = "com.android.perftests.core.TestPermission";
+    private static final String PERMISSION_DOESNT_HAVE_NAME =
+            "com.android.perftests.core.TestBadPermission";
+    private static final String THIS_PACKAGE_NAME = "com.android.perftests.core";
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testHasPermission() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Context context = InstrumentationRegistry.getTargetContext();
+        while (state.keepRunning()) {
+            int ret = context.getPackageManager().checkPermission(PERMISSION_HAS_NAME,
+                    THIS_PACKAGE_NAME);
+        }
+    }
+
+    @Test
+    public void testDoesntHavePermission() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Context context = InstrumentationRegistry.getTargetContext();
+
+        while (state.keepRunning()) {
+            int ret = context.getPackageManager().checkPermission(PERMISSION_DOESNT_HAVE_NAME,
+                    THIS_PACKAGE_NAME);
+        }
+    }
+
+}
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index 5653a03..93a0fc3 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -190,7 +190,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final PremeasuredText text = PremeasuredText.build(
+            final MeasuredText text = MeasuredText.build(
                     generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
             state.resumeTiming();
 
@@ -206,7 +206,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final PremeasuredText text = PremeasuredText.build(
+            final MeasuredText text = MeasuredText.build(
                     generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
             state.resumeTiming();
 
@@ -222,7 +222,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final PremeasuredText text = PremeasuredText.build(
+            final MeasuredText text = MeasuredText.build(
                     generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
             state.resumeTiming();
 
@@ -238,7 +238,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final PremeasuredText text = PremeasuredText.build(
+            final MeasuredText text = MeasuredText.build(
                     generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
             state.resumeTiming();
 
@@ -254,7 +254,7 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             state.pauseTiming();
-            final PremeasuredText text = PremeasuredText.build(
+            final MeasuredText text = MeasuredText.build(
                     generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR);
             state.resumeTiming();
 
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
index bb9dc4a..da17818 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
@@ -25,7 +25,6 @@
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -78,10 +77,7 @@
 
     // Statistics. These values will be filled when the benchmark has finished.
     // The computation needs double precision, but long int is fine for final reporting.
-    private long mMedian = 0;
-    private double mMean = 0.0;
-    private double mStandardDeviation = 0.0;
-    private long mMin = 0;
+    private Stats mStats;
 
     // Individual duration in nano seconds.
     private ArrayList<Long> mResults = new ArrayList<>();
@@ -90,36 +86,6 @@
         return TimeUnit.MILLISECONDS.toNanos(ms);
     }
 
-    /**
-     * Calculates statistics.
-     */
-    private void calculateSatistics() {
-        final int size = mResults.size();
-        if (size <= 1) {
-            throw new IllegalStateException("At least two results are necessary.");
-        }
-
-        Collections.sort(mResults);
-        mMedian = size % 2 == 0 ? (mResults.get(size / 2) + mResults.get(size / 2 + 1)) / 2 :
-                mResults.get(size / 2);
-
-        mMin = mResults.get(0);
-        for (int i = 0; i < size; ++i) {
-            long result = mResults.get(i);
-            mMean += result;
-            if (result < mMin) {
-                mMin = result;
-            }
-        }
-        mMean /= (double) size;
-
-        for (int i = 0; i < size; ++i) {
-            final double tmp = mResults.get(i) - mMean;
-            mStandardDeviation += tmp * tmp;
-        }
-        mStandardDeviation = Math.sqrt(mStandardDeviation / (double) (size - 1));
-    }
-
     // Stops the benchmark timer.
     // This method can be called only when the timer is running.
     public void pauseTiming() {
@@ -173,7 +139,7 @@
             if (ENABLE_PROFILING) {
                 Debug.stopMethodTracing();
             }
-            calculateSatistics();
+            mStats = new Stats(mResults);
             mState = FINISHED;
             return false;
         }
@@ -224,28 +190,28 @@
         if (mState != FINISHED) {
             throw new IllegalStateException("The benchmark hasn't finished");
         }
-        return (long) mMean;
+        return (long) mStats.getMean();
     }
 
     private long median() {
         if (mState != FINISHED) {
             throw new IllegalStateException("The benchmark hasn't finished");
         }
-        return mMedian;
+        return mStats.getMedian();
     }
 
     private long min() {
         if (mState != FINISHED) {
             throw new IllegalStateException("The benchmark hasn't finished");
         }
-        return mMin;
+        return mStats.getMin();
     }
 
     private long standardDeviation() {
         if (mState != FINISHED) {
             throw new IllegalStateException("The benchmark hasn't finished");
         }
-        return (long) mStandardDeviation;
+        return (long) mStats.getStandardDeviation();
     }
 
     private String summaryLine() {
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
new file mode 100644
index 0000000..2c84db1
--- /dev/null
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+
+package android.perftests.utils;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Provides a benchmark framework.
+ *
+ * This differs from BenchmarkState in that rather than the class measuring the the elapsed time,
+ * the test passes in the elapsed time.
+ *
+ * Example usage:
+ *
+ * public void sampleMethod() {
+ *     ManualBenchmarkState state = new ManualBenchmarkState();
+ *
+ *     int[] src = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ *     long elapsedTime = 0;
+ *     while (state.keepRunning(elapsedTime)) {
+ *         long startTime = System.nanoTime();
+ *         int[] dest = new int[src.length];
+ *         System.arraycopy(src, 0, dest, 0, src.length);
+ *         elapsedTime = System.nanoTime() - startTime;
+ *     }
+ *     System.out.println(state.summaryLine());
+ * }
+ *
+ * Or use the PerfManualStatusReporter TestRule.
+ *
+ * Make sure that the overhead of checking the clock does not noticeably affect the results.
+ */
+public final class ManualBenchmarkState {
+    private static final String TAG = ManualBenchmarkState.class.getSimpleName();
+
+    // TODO: Tune these values.
+    // warm-up for duration
+    private static final long WARMUP_DURATION_NS = TimeUnit.SECONDS.toNanos(5);
+    // minimum iterations to warm-up for
+    private static final int WARMUP_MIN_ITERATIONS = 8;
+
+    // target testing for duration
+    private static final long TARGET_TEST_DURATION_NS = TimeUnit.SECONDS.toNanos(16);
+    private static final int MAX_TEST_ITERATIONS = 1000000;
+    private static final int MIN_TEST_ITERATIONS = 10;
+
+    private static final int NOT_STARTED = 0;  // The benchmark has not started yet.
+    private static final int WARMUP = 1; // The benchmark is warming up.
+    private static final int RUNNING = 2;  // The benchmark is running.
+    private static final int FINISHED = 3;  // The benchmark has stopped.
+
+    private int mState = NOT_STARTED;  // Current benchmark state.
+
+    private long mWarmupStartTime = 0;
+    private int mWarmupIterations = 0;
+
+    private int mMaxIterations = 0;
+
+    // Individual duration in nano seconds.
+    private ArrayList<Long> mResults = new ArrayList<>();
+
+    // Statistics. These values will be filled when the benchmark has finished.
+    // The computation needs double precision, but long int is fine for final reporting.
+    private Stats mStats;
+
+    private void beginBenchmark(long warmupDuration, int iterations) {
+        mMaxIterations = (int) (TARGET_TEST_DURATION_NS / (warmupDuration / iterations));
+        mMaxIterations = Math.min(MAX_TEST_ITERATIONS,
+                Math.max(mMaxIterations, MIN_TEST_ITERATIONS));
+        mState = RUNNING;
+    }
+
+    /**
+     * Judges whether the benchmark needs more samples.
+     *
+     * For the usage, see class comment.
+     */
+    public boolean keepRunning(long duration) {
+        if (duration < 0) {
+            throw new RuntimeException("duration is negative: " + duration);
+        }
+        switch (mState) {
+            case NOT_STARTED:
+                mState = WARMUP;
+                mWarmupStartTime = System.nanoTime();
+                return true;
+            case WARMUP: {
+                final long timeSinceStartingWarmup = System.nanoTime() - mWarmupStartTime;
+                ++mWarmupIterations;
+                if (mWarmupIterations >= WARMUP_MIN_ITERATIONS
+                        && timeSinceStartingWarmup >= WARMUP_DURATION_NS) {
+                    beginBenchmark(timeSinceStartingWarmup, mWarmupIterations);
+                }
+                return true;
+            }
+            case RUNNING: {
+                mResults.add(duration);
+                final boolean keepRunning = mResults.size() < mMaxIterations;
+                if (!keepRunning) {
+                    mStats = new Stats(mResults);
+                    mState = FINISHED;
+                }
+                return keepRunning;
+            }
+            case FINISHED:
+                throw new IllegalStateException("The benchmark has finished.");
+            default:
+                throw new IllegalStateException("The benchmark is in an unknown state.");
+        }
+    }
+
+    private String summaryLine() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("Summary: ");
+        sb.append("median=").append(mStats.getMedian()).append("ns, ");
+        sb.append("mean=").append(mStats.getMean()).append("ns, ");
+        sb.append("min=").append(mStats.getMin()).append("ns, ");
+        sb.append("max=").append(mStats.getMax()).append("ns, ");
+        sb.append("sigma=").append(mStats.getStandardDeviation()).append(", ");
+        sb.append("iteration=").append(mResults.size()).append(", ");
+        sb.append("values=").append(mResults.toString());
+        return sb.toString();
+    }
+
+    public void sendFullStatusReport(Instrumentation instrumentation, String key) {
+        if (mState != FINISHED) {
+            throw new IllegalStateException("The benchmark hasn't finished");
+        }
+        Log.i(TAG, key + summaryLine());
+        final Bundle status = new Bundle();
+        status.putLong(key + "_median", mStats.getMedian());
+        status.putLong(key + "_mean", (long) mStats.getMean());
+        status.putLong(key + "_stddev", (long) mStats.getStandardDeviation());
+        instrumentation.sendStatus(Activity.RESULT_OK, status);
+    }
+}
+
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/PerfManualStatusReporter.java b/apct-tests/perftests/utils/src/android/perftests/utils/PerfManualStatusReporter.java
new file mode 100644
index 0000000..0de6f1d
--- /dev/null
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/PerfManualStatusReporter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+package android.perftests.utils;
+
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Use this rule to make sure we report the status after the test success.
+ *
+ * <code>
+ *
+ * @Rule public PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
+ * @Test public void functionName() {
+ *     ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ *
+ *     int[] src = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ *     long elapsedTime = 0;
+ *     while (state.keepRunning(elapsedTime)) {
+ *         long startTime = System.nanoTime();
+ *         int[] dest = new int[src.length];
+ *         System.arraycopy(src, 0, dest, 0, src.length);
+ *         elapsedTime = System.nanoTime() - startTime;
+ *     }
+ * }
+ * </code>
+ *
+ * When test succeeded, the status report will use the key as
+ * "functionName_*"
+ */
+
+public class PerfManualStatusReporter implements TestRule {
+    private final ManualBenchmarkState mState;
+
+    public PerfManualStatusReporter() {
+        mState = new ManualBenchmarkState();
+    }
+
+    public ManualBenchmarkState getBenchmarkState() {
+        return mState;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                base.evaluate();
+
+                mState.sendFullStatusReport(InstrumentationRegistry.getInstrumentation(),
+                        description.getMethodName());
+            }
+        };
+    }
+}
+
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
new file mode 100644
index 0000000..acc44a8
--- /dev/null
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+package android.perftests.utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class Stats {
+    private long mMedian, mMin, mMax;
+    private double mMean, mStandardDeviation;
+
+    /* 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.");
+        }
+
+        Collections.sort(values);
+
+        mMedian = size % 2 == 0 ? (values.get(size / 2) + values.get(size / 2 - 1)) / 2 :
+                values.get(size / 2);
+
+        mMin = values.get(0);
+        mMax = values.get(values.size() - 1);
+
+        for (int i = 0; i < size; ++i) {
+            long result = values.get(i);
+            mMean += result;
+        }
+        mMean /= (double) size;
+
+        for (int i = 0; i < size; ++i) {
+            final double tmp = values.get(i) - mMean;
+            mStandardDeviation += tmp * tmp;
+        }
+        mStandardDeviation = Math.sqrt(mStandardDeviation / (double) (size - 1));
+    }
+
+    public double getMean() {
+        return mMean;
+    }
+
+    public long getMedian() {
+        return mMedian;
+    }
+
+    public long getMax() {
+        return mMax;
+    }
+
+    public long getMin() {
+        return mMin;
+    }
+
+    public double getStandardDeviation() {
+        return mStandardDeviation;
+    }
+}
diff --git a/api/current.txt b/api/current.txt
index 484ca48..77241da 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -212,6 +212,7 @@
     field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
     field public static final int accessibilityFlags = 16843652; // 0x1010384
     field public static final int accessibilityLiveRegion = 16843758; // 0x10103ee
+    field public static final int accessibilityPaneTitle = 16844156; // 0x101057c
     field public static final int accessibilityTraversalAfter = 16843986; // 0x10104d2
     field public static final int accessibilityTraversalBefore = 16843985; // 0x10104d1
     field public static final int accountPreferences = 16843423; // 0x101029f
@@ -5290,8 +5291,19 @@
     method public android.os.Bundle getExtras();
     method public android.graphics.drawable.Icon getIcon();
     method public android.app.RemoteInput[] getRemoteInputs();
+    method public int getSemanticAction();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.Notification.Action> CREATOR;
+    field public static final int SEMANTIC_ACTION_ARCHIVE = 5; // 0x5
+    field public static final int SEMANTIC_ACTION_DELETE = 4; // 0x4
+    field public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; // 0x2
+    field public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; // 0x3
+    field public static final int SEMANTIC_ACTION_MUTE = 6; // 0x6
+    field public static final int SEMANTIC_ACTION_NONE = 0; // 0x0
+    field public static final int SEMANTIC_ACTION_REPLY = 1; // 0x1
+    field public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; // 0x9
+    field public static final int SEMANTIC_ACTION_THUMBS_UP = 8; // 0x8
+    field public static final int SEMANTIC_ACTION_UNMUTE = 7; // 0x7
     field public android.app.PendingIntent actionIntent;
     field public deprecated int icon;
     field public java.lang.CharSequence title;
@@ -5307,6 +5319,7 @@
     method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Extender);
     method public android.os.Bundle getExtras();
     method public android.app.Notification.Action.Builder setAllowGeneratedReplies(boolean);
+    method public android.app.Notification.Action.Builder setSemanticAction(int);
   }
 
   public static abstract interface Notification.Action.Extender {
@@ -6531,6 +6544,7 @@
     method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle);
     method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean);
     method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap);
+    method public boolean startUserInBackground(android.content.ComponentName, android.os.UserHandle);
     method public boolean stopUser(android.content.ComponentName, android.os.UserHandle);
     method public boolean switchUser(android.content.ComponentName, android.os.UserHandle);
     method public void transferOwnership(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle);
@@ -6643,7 +6657,6 @@
     field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
     field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
     field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
-    field public static final int START_USER_IN_BACKGROUND = 8; // 0x8
     field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
     field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
   }
@@ -20869,7 +20882,6 @@
     method public int getMaxWidth();
     method public java.lang.CharSequence getTextForImeAction(int);
     method public android.app.Dialog getWindow();
-    method public void hideSoftInputFromInputMethod(int);
     method public void hideStatusIcon();
     method public void hideWindow();
     method public boolean isExtractViewShown();
@@ -20917,6 +20929,7 @@
     method public void onWindowHidden();
     method public void onWindowShown();
     method public void requestHideSelf(int);
+    method public void requestShowSelf(int);
     method public boolean sendDefaultEditorAction(boolean);
     method public void sendDownUpKeyEvents(int);
     method public void sendKeyChar(char);
@@ -20929,7 +20942,6 @@
     method public void setInputMethodAndSubtype(java.lang.String, android.view.inputmethod.InputMethodSubtype);
     method public void setInputView(android.view.View);
     method public boolean shouldOfferSwitchingToNextInputMethod();
-    method public void showSoftInputFromInputMethod(int);
     method public void showStatusIcon(int);
     method public void showWindow(boolean);
     method public void switchInputMethod(java.lang.String);
@@ -26327,10 +26339,10 @@
   }
 
   public final class MacAddress implements android.os.Parcelable {
-    method public int addressType();
     method public int describeContents();
     method public static android.net.MacAddress fromBytes(byte[]);
     method public static android.net.MacAddress fromString(java.lang.String);
+    method public int getAddressType();
     method public boolean isLocallyAssigned();
     method public byte[] toByteArray();
     method public java.lang.String toOuiString();
@@ -26340,7 +26352,6 @@
     field public static final int TYPE_BROADCAST = 3; // 0x3
     field public static final int TYPE_MULTICAST = 2; // 0x2
     field public static final int TYPE_UNICAST = 1; // 0x1
-    field public static final int TYPE_UNKNOWN = 0; // 0x0
   }
 
   public class MailTo {
@@ -32120,6 +32131,7 @@
   }
 
   public final class PowerManager {
+    method public int getLocationPowerSaveMode();
     method public boolean isDeviceIdleMode();
     method public boolean isIgnoringBatteryOptimizations(java.lang.String);
     method public boolean isInteractive();
@@ -32133,6 +32145,10 @@
     field public static final java.lang.String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
     field public static final java.lang.String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED";
     field public static final deprecated int FULL_WAKE_LOCK = 26; // 0x1a
+    field public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2; // 0x2
+    field public static final int LOCATION_MODE_FOREGROUND_ONLY = 3; // 0x3
+    field public static final int LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF = 1; // 0x1
+    field public static final int LOCATION_MODE_NO_CHANGE = 0; // 0x0
     field public static final int ON_AFTER_RELEASE = 536870912; // 0x20000000
     field public static final int PARTIAL_WAKE_LOCK = 1; // 0x1
     field public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32; // 0x20
@@ -32420,22 +32436,24 @@
     method public boolean isUserRunningOrStopping(android.os.UserHandle);
     method public boolean isUserUnlocked();
     method public boolean isUserUnlocked(android.os.UserHandle);
+    method public boolean requestQuietModeEnabled(boolean, android.os.UserHandle);
     method public deprecated boolean setRestrictionsChallenge(java.lang.String);
     method public deprecated void setUserRestriction(java.lang.String, boolean);
     method public deprecated void setUserRestrictions(android.os.Bundle);
     method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
     method public static boolean supportsMultipleUsers();
-    method public boolean trySetQuietModeEnabled(boolean, android.os.UserHandle);
     field public static final java.lang.String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
     field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
     field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
     field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
     field public static final java.lang.String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
+    field public static final java.lang.String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
     field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps";
     field public static final java.lang.String DISALLOW_AUTOFILL = "no_autofill";
     field public static final java.lang.String DISALLOW_BLUETOOTH = "no_bluetooth";
     field public static final java.lang.String DISALLOW_BLUETOOTH_SHARING = "no_bluetooth_sharing";
     field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
+    field public static final java.lang.String DISALLOW_CONFIG_BRIGHTNESS = "no_config_brightness";
     field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
     field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
     field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
@@ -32463,6 +32481,7 @@
     field public static final java.lang.String DISALLOW_SAFE_BOOT = "no_safe_boot";
     field public static final java.lang.String DISALLOW_SET_USER_ICON = "no_set_user_icon";
     field public static final java.lang.String DISALLOW_SET_WALLPAPER = "no_set_wallpaper";
+    field public static final java.lang.String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile";
     field public static final java.lang.String DISALLOW_SHARE_LOCATION = "no_share_location";
     field public static final java.lang.String DISALLOW_SMS = "no_sms";
     field public static final java.lang.String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
@@ -41126,6 +41145,7 @@
     method public void onServiceStateChanged(android.telephony.ServiceState);
     method public deprecated void onSignalStrengthChanged(int);
     method public void onSignalStrengthsChanged(android.telephony.SignalStrength);
+    method public void onUserMobileDataStateChanged(boolean);
     field public static final int LISTEN_CALL_FORWARDING_INDICATOR = 8; // 0x8
     field public static final int LISTEN_CALL_STATE = 32; // 0x20
     field public static final int LISTEN_CELL_INFO = 1024; // 0x400
@@ -41137,6 +41157,7 @@
     field public static final int LISTEN_SERVICE_STATE = 1; // 0x1
     field public static final deprecated int LISTEN_SIGNAL_STRENGTH = 2; // 0x2
     field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
+    field public static final int LISTEN_USER_MOBILE_DATA_STATE = 524288; // 0x80000
   }
 
   public final class RadioAccessSpecifier implements android.os.Parcelable {
@@ -42273,20 +42294,9 @@
     method public boolean isAllowed(char);
   }
 
-  public abstract interface NoCopySpan {
-  }
-
-  public static class NoCopySpan.Concrete implements android.text.NoCopySpan {
-    ctor public NoCopySpan.Concrete();
-  }
-
-  public abstract interface ParcelableSpan implements android.os.Parcelable {
-    method public abstract int getSpanTypeId();
-  }
-
-  public class PremeasuredText implements android.text.Spanned {
-    method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic);
-    method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int);
+  public class MeasuredText implements android.text.Spanned {
+    method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic);
+    method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int);
     method public char charAt(int);
     method public int getEnd();
     method public android.text.TextPaint getPaint();
@@ -42305,6 +42315,17 @@
     method public java.lang.CharSequence subSequence(int, int);
   }
 
+  public abstract interface NoCopySpan {
+  }
+
+  public static class NoCopySpan.Concrete implements android.text.NoCopySpan {
+    ctor public NoCopySpan.Concrete();
+  }
+
+  public abstract interface ParcelableSpan implements android.os.Parcelable {
+    method public abstract int getSpanTypeId();
+  }
+
   public class Selection {
     method public static boolean extendDown(android.text.Spannable, android.text.Layout);
     method public static boolean extendLeft(android.text.Spannable, android.text.Layout);
@@ -45245,6 +45266,7 @@
     field public static final int KEYCODE_8 = 15; // 0xf
     field public static final int KEYCODE_9 = 16; // 0x10
     field public static final int KEYCODE_A = 29; // 0x1d
+    field public static final int KEYCODE_ALL_APPS = 284; // 0x11c
     field public static final int KEYCODE_ALT_LEFT = 57; // 0x39
     field public static final int KEYCODE_ALT_RIGHT = 58; // 0x3a
     field public static final int KEYCODE_APOSTROPHE = 75; // 0x4b
@@ -46290,6 +46312,7 @@
     method public java.lang.CharSequence getAccessibilityClassName();
     method public int getAccessibilityLiveRegion();
     method public android.view.accessibility.AccessibilityNodeProvider getAccessibilityNodeProvider();
+    method public java.lang.CharSequence getAccessibilityPaneTitle();
     method public int getAccessibilityTraversalAfter();
     method public int getAccessibilityTraversalBefore();
     method public float getAlpha();
@@ -46613,6 +46636,7 @@
     method public void sendAccessibilityEventUnchecked(android.view.accessibility.AccessibilityEvent);
     method public void setAccessibilityDelegate(android.view.View.AccessibilityDelegate);
     method public void setAccessibilityLiveRegion(int);
+    method public void setAccessibilityPaneTitle(java.lang.CharSequence);
     method public void setAccessibilityTraversalAfter(int);
     method public void setAccessibilityTraversalBefore(int);
     method public void setActivated(boolean);
@@ -48010,6 +48034,7 @@
     method public void setPackageName(java.lang.CharSequence);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+    field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
     field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1
     field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2
     field public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0; // 0x0
@@ -48120,6 +48145,7 @@
     method public int getMaxTextLength();
     method public int getMovementGranularities();
     method public java.lang.CharSequence getPackageName();
+    method public java.lang.CharSequence getPaneTitle();
     method public android.view.accessibility.AccessibilityNodeInfo getParent();
     method public android.view.accessibility.AccessibilityNodeInfo.RangeInfo getRangeInfo();
     method public java.lang.CharSequence getText();
@@ -48197,6 +48223,7 @@
     method public void setMovementGranularities(int);
     method public void setMultiLine(boolean);
     method public void setPackageName(java.lang.CharSequence);
+    method public void setPaneTitle(java.lang.CharSequence);
     method public void setParent(android.view.View);
     method public void setParent(android.view.View, int);
     method public void setPassword(boolean);
@@ -49233,14 +49260,13 @@
     method public android.graphics.drawable.Drawable getSecondaryIcon(int);
     method public android.content.Intent getSecondaryIntent(int);
     method public java.lang.CharSequence getSecondaryLabel(int);
-    method public android.view.View.OnClickListener getSecondaryOnClickListener(int);
     method public java.lang.String getSignature();
     method public java.lang.String getText();
   }
 
   public static final class TextClassification.Builder {
     ctor public TextClassification.Builder();
-    method public android.view.textclassifier.TextClassification.Builder addSecondaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener);
+    method public android.view.textclassifier.TextClassification.Builder addSecondaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable);
     method public android.view.textclassifier.TextClassification build();
     method public android.view.textclassifier.TextClassification.Builder clearSecondaryActions();
     method public android.view.textclassifier.TextClassification.Builder setEntityType(java.lang.String, float);
@@ -49248,15 +49274,18 @@
     method public android.view.textclassifier.TextClassification.Builder setIntent(android.content.Intent);
     method public android.view.textclassifier.TextClassification.Builder setLabel(java.lang.String);
     method public android.view.textclassifier.TextClassification.Builder setOnClickListener(android.view.View.OnClickListener);
-    method public android.view.textclassifier.TextClassification.Builder setPrimaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener);
+    method public android.view.textclassifier.TextClassification.Builder setPrimaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable);
     method public android.view.textclassifier.TextClassification.Builder setSignature(java.lang.String);
     method public android.view.textclassifier.TextClassification.Builder setText(java.lang.String);
   }
 
-  public static final class TextClassification.Options {
+  public static final class TextClassification.Options implements android.os.Parcelable {
     ctor public TextClassification.Options();
+    method public int describeContents();
     method public android.os.LocaleList getDefaultLocales();
     method public android.view.textclassifier.TextClassification.Options setDefaultLocales(android.os.LocaleList);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassification.Options> CREATOR;
   }
 
   public final class TextClassificationManager {
@@ -49286,16 +49315,22 @@
     field public static final java.lang.String TYPE_URL = "url";
   }
 
-  public static final class TextClassifier.EntityConfig {
+  public static final class TextClassifier.EntityConfig implements android.os.Parcelable {
     ctor public TextClassifier.EntityConfig(int);
+    method public int describeContents();
     method public android.view.textclassifier.TextClassifier.EntityConfig excludeEntities(java.lang.String...);
     method public java.util.List<java.lang.String> getEntities(android.view.textclassifier.TextClassifier);
     method public android.view.textclassifier.TextClassifier.EntityConfig includeEntities(java.lang.String...);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassifier.EntityConfig> CREATOR;
   }
 
-  public final class TextLinks {
+  public final class TextLinks implements android.os.Parcelable {
     method public boolean apply(android.text.SpannableString, java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.text.style.ClickableSpan>);
+    method public int describeContents();
     method public java.util.Collection<android.view.textclassifier.TextLinks.TextLink> getLinks();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks> CREATOR;
   }
 
   public static final class TextLinks.Builder {
@@ -49304,21 +49339,27 @@
     method public android.view.textclassifier.TextLinks build();
   }
 
-  public static final class TextLinks.Options {
+  public static final class TextLinks.Options implements android.os.Parcelable {
     ctor public TextLinks.Options();
+    method public int describeContents();
     method public android.os.LocaleList getDefaultLocales();
     method public android.view.textclassifier.TextClassifier.EntityConfig getEntityConfig();
     method public android.view.textclassifier.TextLinks.Options setDefaultLocales(android.os.LocaleList);
     method public android.view.textclassifier.TextLinks.Options setEntityConfig(android.view.textclassifier.TextClassifier.EntityConfig);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks.Options> CREATOR;
   }
 
-  public static final class TextLinks.TextLink {
+  public static final class TextLinks.TextLink implements android.os.Parcelable {
     ctor public TextLinks.TextLink(java.lang.String, int, int, java.util.Map<java.lang.String, java.lang.Float>);
+    method public int describeContents();
     method public float getConfidenceScore(java.lang.String);
     method public int getEnd();
     method public java.lang.String getEntity(int);
     method public int getEntityCount();
     method public int getStart();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks.TextLink> CREATOR;
   }
 
   public final class TextSelection {
@@ -49337,10 +49378,13 @@
     method public android.view.textclassifier.TextSelection.Builder setSignature(java.lang.String);
   }
 
-  public static final class TextSelection.Options {
+  public static final class TextSelection.Options implements android.os.Parcelable {
     ctor public TextSelection.Options();
+    method public int describeContents();
     method public android.os.LocaleList getDefaultLocales();
     method public android.view.textclassifier.TextSelection.Options setDefaultLocales(android.os.LocaleList);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextSelection.Options> CREATOR;
   }
 
 }
diff --git a/api/removed.txt b/api/removed.txt
index 2aab223..77088e5 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -265,6 +265,7 @@
     method public android.graphics.drawable.Drawable getBadgedDrawableForUser(android.graphics.drawable.Drawable, android.os.UserHandle, android.graphics.Rect, int);
     method public android.graphics.drawable.Drawable getBadgedIconForUser(android.graphics.drawable.Drawable, android.os.UserHandle);
     method public java.lang.CharSequence getBadgedLabelForUser(java.lang.CharSequence, android.os.UserHandle);
+    method public deprecated boolean trySetQuietModeEnabled(boolean, android.os.UserHandle);
   }
 
 }
diff --git a/api/system-current.txt b/api/system-current.txt
index 74c457d..002c2bd 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -33,6 +33,7 @@
     field public static final java.lang.String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
     field public static final java.lang.String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED";
     field public static final java.lang.String BRICK = "android.permission.BRICK";
+    field public static final java.lang.String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
     field public static final deprecated java.lang.String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED";
     field public static final java.lang.String CALL_PRIVILEGED = "android.permission.CALL_PRIVILEGED";
     field public static final java.lang.String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
@@ -46,6 +47,7 @@
     field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION";
     field public static final java.lang.String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST";
     field public static final java.lang.String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
+    field public static final java.lang.String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
     field public static final java.lang.String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL";
     field public static final java.lang.String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
     field public static final java.lang.String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE";
@@ -153,6 +155,7 @@
     field public static final java.lang.String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
     field public static final java.lang.String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE";
     field public static final java.lang.String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP";
+    field public static final java.lang.String SET_HARMFUL_APP_WARNINGS = "android.permission.SET_HARMFUL_APP_WARNINGS";
     field public static final java.lang.String SET_MEDIA_KEY_LISTENER = "android.permission.SET_MEDIA_KEY_LISTENER";
     field public static final java.lang.String SET_ORIENTATION = "android.permission.SET_ORIENTATION";
     field public static final java.lang.String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
@@ -161,6 +164,7 @@
     field public static final java.lang.String SET_TIME = "android.permission.SET_TIME";
     field public static final java.lang.String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
     field public static final java.lang.String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
+    field public static final java.lang.String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
     field public static final java.lang.String SHUTDOWN = "android.permission.SHUTDOWN";
     field public static final java.lang.String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES";
     field public static final java.lang.String STATUS_BAR = "android.permission.STATUS_BAR";
@@ -190,10 +194,6 @@
     field public static final int isVrOnly = 16844152; // 0x1010578
     field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
     field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566
-    field public static final int searchKeyphrase = 16843871; // 0x101045f
-    field public static final int searchKeyphraseId = 16843870; // 0x101045e
-    field public static final int searchKeyphraseRecognitionFlags = 16843942; // 0x10104a6
-    field public static final int searchKeyphraseSupportedLocales = 16843872; // 0x1010460
   }
 
   public static final class R.raw {
@@ -317,6 +317,7 @@
 
   public class KeyguardManager {
     method public android.content.Intent createConfirmFactoryResetCredentialIntent(java.lang.CharSequence, java.lang.CharSequence, java.lang.CharSequence);
+    method public void requestDismissKeyguard(android.app.Activity, java.lang.CharSequence, android.app.KeyguardManager.KeyguardDismissCallback);
   }
 
   public class Notification implements android.os.Parcelable {
@@ -688,6 +689,9 @@
     method public boolean isEncrypted();
     method public boolean removeBond();
     method public boolean setPhonebookAccessPermission(int);
+    field public static final int ACCESS_ALLOWED = 1; // 0x1
+    field public static final int ACCESS_REJECTED = 2; // 0x2
+    field public static final int ACCESS_UNKNOWN = 0; // 0x0
   }
 
   public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
@@ -696,6 +700,11 @@
     method public boolean setPriority(android.bluetooth.BluetoothDevice, int);
   }
 
+  public abstract interface BluetoothProfile {
+    field public static final int PRIORITY_OFF = 0; // 0x0
+    field public static final int PRIORITY_ON = 100; // 0x64
+  }
+
 }
 
 package android.bluetooth.le {
@@ -900,6 +909,7 @@
     method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
     method public android.content.pm.dex.ArtManager getArtManager();
     method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
+    method public java.lang.CharSequence getHarmfulAppWarning(java.lang.String);
     method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
     method public abstract android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
     method public abstract android.content.ComponentName getInstantAppInstallerComponent();
@@ -916,6 +926,7 @@
     method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
     method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
+    method public void setHarmfulAppWarning(java.lang.String, java.lang.CharSequence);
     method public abstract void setUpdateAvailable(java.lang.String, boolean);
     method public abstract boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
     method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
@@ -1083,8 +1094,38 @@
 
 package android.hardware.display {
 
+  public final class BrightnessChangeEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessChangeEvent> CREATOR;
+    field public final float batteryLevel;
+    field public final float brightness;
+    field public final int colorTemperature;
+    field public final float lastBrightness;
+    field public final long[] luxTimestamps;
+    field public final float[] luxValues;
+    field public final boolean nightMode;
+    field public final java.lang.String packageName;
+    field public final long timeStamp;
+  }
+
+  public final class BrightnessConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.util.Pair<float[], float[]> getCurve();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessConfiguration> CREATOR;
+  }
+
+  public static class BrightnessConfiguration.Builder {
+    ctor public BrightnessConfiguration.Builder();
+    method public android.hardware.display.BrightnessConfiguration build();
+    method public android.hardware.display.BrightnessConfiguration.Builder setCurve(float[], float[]);
+  }
+
   public final class DisplayManager {
+    method public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents();
     method public android.graphics.Point getStableDisplaySize();
+    method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
   }
 
 }
@@ -1692,6 +1733,39 @@
 
 package android.hardware.radio {
 
+  public final class ProgramList implements java.lang.AutoCloseable {
+    method public void addOnCompleteListener(java.util.concurrent.Executor, android.hardware.radio.ProgramList.OnCompleteListener);
+    method public void addOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener);
+    method public void close();
+    method public android.hardware.radio.RadioManager.ProgramInfo get(android.hardware.radio.ProgramSelector.Identifier);
+    method public void registerListCallback(java.util.concurrent.Executor, android.hardware.radio.ProgramList.ListCallback);
+    method public void registerListCallback(android.hardware.radio.ProgramList.ListCallback);
+    method public void removeOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener);
+    method public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> toList();
+    method public void unregisterListCallback(android.hardware.radio.ProgramList.ListCallback);
+  }
+
+  public static final class ProgramList.Filter implements android.os.Parcelable {
+    ctor public ProgramList.Filter(java.util.Set<java.lang.Integer>, java.util.Set<android.hardware.radio.ProgramSelector.Identifier>, boolean, boolean);
+    method public boolean areCategoriesIncluded();
+    method public boolean areModificationsExcluded();
+    method public int describeContents();
+    method public java.util.Set<java.lang.Integer> getIdentifierTypes();
+    method public java.util.Set<android.hardware.radio.ProgramSelector.Identifier> getIdentifiers();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramList.Filter> CREATOR;
+  }
+
+  public static abstract class ProgramList.ListCallback {
+    ctor public ProgramList.ListCallback();
+    method public void onItemChanged(android.hardware.radio.ProgramSelector.Identifier);
+    method public void onItemRemoved(android.hardware.radio.ProgramSelector.Identifier);
+  }
+
+  public static abstract interface ProgramList.OnCompleteListener {
+    method public abstract void onComplete();
+  }
+
   public final class ProgramSelector implements android.os.Parcelable {
     ctor public ProgramSelector(int, android.hardware.radio.ProgramSelector.Identifier, android.hardware.radio.ProgramSelector.Identifier[], long[]);
     method public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int);
@@ -1715,6 +1789,7 @@
     field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
     field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3
     field public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
+    field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0
     field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
     field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
     field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
@@ -1726,6 +1801,7 @@
     field public static final int PROGRAM_TYPE_DRMO = 6; // 0x6
     field public static final int PROGRAM_TYPE_FM = 2; // 0x2
     field public static final int PROGRAM_TYPE_FM_HD = 4; // 0x4
+    field public static final int PROGRAM_TYPE_INVALID = 0; // 0x0
     field public static final int PROGRAM_TYPE_SXM = 7; // 0x7
     field public static final int PROGRAM_TYPE_VENDOR_END = 1999; // 0x7cf
     field public static final int PROGRAM_TYPE_VENDOR_START = 1000; // 0x3e8
@@ -1944,10 +2020,11 @@
     method public abstract void cancelAnnouncement();
     method public abstract void close();
     method public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]);
+    method public android.hardware.radio.ProgramList getDynamicProgramList(android.hardware.radio.ProgramList.Filter);
     method public abstract boolean getMute();
     method public java.util.Map<java.lang.String, java.lang.String> getParameters(java.util.List<java.lang.String>);
     method public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]);
-    method public abstract java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>);
+    method public abstract deprecated java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>);
     method public abstract boolean hasControl();
     method public abstract deprecated boolean isAnalogForced();
     method public abstract boolean isAntennaConnected();
@@ -4386,6 +4463,8 @@
     method public int[] supplyPukReportResult(java.lang.String, java.lang.String);
     method public void toggleRadioOnOff();
     method public void updateServiceLocation();
+    method public void setSimPowerState(int);
+    method public void setSimPowerStateForSlot(int, int);
     field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
     field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
@@ -4409,11 +4488,11 @@
 package android.telephony.data {
 
   public final class DataCallResponse implements android.os.Parcelable {
-    ctor public DataCallResponse(int, int, int, int, java.lang.String, java.lang.String, java.util.List<android.telephony.data.InterfaceAddress>, java.util.List<java.net.InetAddress>, java.util.List<java.net.InetAddress>, java.util.List<java.lang.String>, int);
+    ctor public DataCallResponse(int, int, int, int, java.lang.String, java.lang.String, java.util.List<android.net.LinkAddress>, java.util.List<java.net.InetAddress>, java.util.List<java.net.InetAddress>, java.util.List<java.lang.String>, int);
     ctor public DataCallResponse(android.os.Parcel);
     method public int describeContents();
     method public int getActive();
-    method public java.util.List<android.telephony.data.InterfaceAddress> getAddresses();
+    method public java.util.List<android.net.LinkAddress> getAddresses();
     method public int getCallId();
     method public java.util.List<java.net.InetAddress> getDnses();
     method public java.util.List<java.net.InetAddress> getGateways();
@@ -4456,17 +4535,6 @@
     field public static final int TYPE_COMMON = 0; // 0x0
   }
 
-  public final class InterfaceAddress implements android.os.Parcelable {
-    ctor public InterfaceAddress(java.net.InetAddress, int);
-    ctor public InterfaceAddress(java.lang.String, int) throws java.net.UnknownHostException;
-    ctor public InterfaceAddress(android.os.Parcel);
-    method public int describeContents();
-    method public java.net.InetAddress getAddress();
-    method public int getNetworkPrefixLength();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.telephony.data.InterfaceAddress> CREATOR;
-  }
-
 }
 
 package android.telephony.ims {
diff --git a/api/system-removed.txt b/api/system-removed.txt
index a3bf576..b63703d 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -126,25 +126,6 @@
     field public boolean untrusted;
   }
 
-  public class WifiConnectionStatistics implements android.os.Parcelable {
-    ctor public WifiConnectionStatistics();
-    ctor public WifiConnectionStatistics(android.net.wifi.WifiConnectionStatistics);
-    method public int describeContents();
-    method public void incrementOrAddUntrusted(java.lang.String, int, int);
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.net.wifi.WifiConnectionStatistics> CREATOR;
-    field public int num24GhzConnected;
-    field public int num5GhzConnected;
-    field public int numAutoJoinAttempt;
-    field public int numAutoRoamAttempt;
-    field public int numWifiManagerJoinAttempt;
-    field public java.util.HashMap<java.lang.String, android.net.wifi.WifiNetworkConnectionStatistics> untrustedNetworkHistory;
-  }
-
-  public class WifiManager {
-    method public android.net.wifi.WifiConnectionStatistics getConnectionStatistics();
-  }
-
 }
 
 package android.os {
diff --git a/api/test-current.txt b/api/test-current.txt
index b16e700..6941731 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -156,6 +156,7 @@
     method public boolean isDeviceManaged();
     field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
     field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
+    field public static final java.lang.String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
     field public static final java.lang.String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
   }
 
@@ -294,6 +295,43 @@
 
 }
 
+package android.hardware.display {
+
+  public final class BrightnessChangeEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessChangeEvent> CREATOR;
+    field public final float batteryLevel;
+    field public final float brightness;
+    field public final int colorTemperature;
+    field public final float lastBrightness;
+    field public final long[] luxTimestamps;
+    field public final float[] luxValues;
+    field public final boolean nightMode;
+    field public final java.lang.String packageName;
+    field public final long timeStamp;
+  }
+
+  public final class BrightnessConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.util.Pair<float[], float[]> getCurve();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessConfiguration> CREATOR;
+  }
+
+  public static class BrightnessConfiguration.Builder {
+    ctor public BrightnessConfiguration.Builder();
+    method public android.hardware.display.BrightnessConfiguration build();
+    method public android.hardware.display.BrightnessConfiguration.Builder setCurve(float[], float[]);
+  }
+
+  public final class DisplayManager {
+    method public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents();
+    method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
+  }
+
+}
+
 package android.location {
 
   public final class GnssClock implements android.os.Parcelable {
@@ -483,6 +521,7 @@
   }
 
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
+    field public static final java.lang.String LOW_POWER_MODE = "low_power";
     field public static final java.lang.String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
   }
 
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index ffe652f..2ebbba6 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -186,7 +186,8 @@
     tests/statsd_test_util.cpp \
     tests/e2e/WakelockDuration_e2e_test.cpp \
     tests/e2e/MetricConditionLink_e2e_test.cpp \
-    tests/e2e/Attribution_e2e_test.cpp
+    tests/e2e/Attribution_e2e_test.cpp \
+    tests/e2e/GaugeMetric_e2e_test.cpp
 
 LOCAL_STATIC_LIBRARIES := \
     $(statsd_common_static_libraries) \
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 1cfec32..9d6d8a1 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -301,7 +301,6 @@
 }
 
 void StatsLogProcessor::WriteDataToDisk() {
-    mkdir(STATS_DATA_DIR, S_IRWXU);
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     for (auto& pair : mMetricsManagers) {
         const ConfigKey& key = pair.first;
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 09e10a1..301e3a5 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -99,6 +99,7 @@
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
+    FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
 
 };
 
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 7f77ef7..4a7f0c4 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -102,7 +102,6 @@
         CpuTimePerUidFreq cpu_time_per_uid_freq = 10010;
         WifiActivityEnergyInfo wifi_activity_energy_info = 10011;
         ModemActivityInfo modem_activity_info = 10012;
-        AttributionChainDummyAtom attribution_chain_dummy_atom = 100000;
     }
 }
 
@@ -147,11 +146,6 @@
  * *****************************************************************************
  */
 
-message AttributionChainDummyAtom {
-    repeated AttributionNode attribution_node = 1;
-    optional int32 value = 2;
-}
-
 /**
  * Logs when the screen state changes.
  *
@@ -218,8 +212,7 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message BleScanStateChanged {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     enum State {
         OFF = 0;
@@ -236,8 +229,7 @@
  */
 // TODO: Consider changing to tracking per-scanner-id (log from AppScanStats).
 message BleUnoptimizedScanStateChanged {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     enum State {
         OFF = 0;
@@ -254,8 +246,7 @@
  */
 // TODO: Consider changing to tracking per-scanner-id (log from AppScanStats).
 message BleScanResultReceived {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     // Number of ble scan results returned.
     optional int32 num_of_results = 2;
@@ -268,8 +259,7 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message SensorStateChanged {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     // TODO: Is there a way to get the actual name of the sensor?
     // The id (int) of the sensor.
@@ -290,8 +280,7 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message GpsScanStateChanged {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     enum State {
         OFF = 0;
@@ -308,8 +297,7 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message SyncStateChanged {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     // Name of the sync (as named in the app)
     optional string name = 2;
@@ -328,8 +316,7 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message ScheduledJobStateChanged {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     // Name of the job (as named in the app)
     optional string name = 2;
@@ -350,8 +337,7 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message AudioStateChanged {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     enum State {
         OFF = 0;
@@ -367,8 +353,7 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message MediaCodecActivityChanged {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     enum State {
         OFF = 0;
@@ -384,8 +369,7 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message FlashlightStateChanged {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     enum State {
         OFF = 0;
@@ -401,8 +385,7 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message CameraStateChanged {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     enum State {
         OFF = 0;
@@ -447,8 +430,7 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message LongPartialWakelockStateChanged {
-    // TODO: Add attribution instead of uid?
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     // The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
     optional string tag = 2;
@@ -605,8 +587,7 @@
  *   frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
  */
 message WakeupAlarmOccurred {
-    // TODO: Add attribution instead of uid?
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     // Name of the wakeup alarm.
     optional string tag = 2;
@@ -673,8 +654,7 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message WifiLockStateChanged {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     enum State {
         OFF = 0;
@@ -708,8 +688,7 @@
  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
  */
 message WifiScanStateChanged {
-    // TODO: Add attribution instead of uid.
-    optional int32 uid = 1;
+    repeated AttributionNode attribution_node = 1;
 
     enum State {
         OFF = 0;
@@ -1109,14 +1088,10 @@
 
 /**
  * Pulls Cpu time per frequency.
- * Note: this should be pulled for gauge metric only, without condition.
- * The puller keeps internal state of last values. It should not be pulled by
- * different metrics.
- * The pulled data is delta of cpu time from last pull, calculated as
- * following:
- * if current time is larger than last value, take delta between the two.
- * if current time is smaller than last value, there must be a cpu
- * hotplug event, and the current time is taken as delta.
+ * Pulls the time the cpu spend on the frequency index. Frequency index
+ * starts from highest to lowest. The value should be monotonically
+ * increasing since boot. However, if there is a cpu
+ * hotplug event, the value would be reset as well.
  */
 message CpuTimePerFreq {
     optional uint32 cluster = 1;
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 184f69e..554ff8a8 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -181,8 +181,6 @@
 }
 
 void ConfigManager::update_saved_configs(const ConfigKey& key, const StatsdConfig& config) {
-    mkdir(STATS_SERVICE_DIR, S_IRWXU);
-
     // If there is a pre-existing config with same key we should first delete it.
     remove_saved_configs(key);
 
diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h
index 845c138..d0f96a2 100644
--- a/cmds/statsd/src/dimension.h
+++ b/cmds/statsd/src/dimension.h
@@ -32,6 +32,10 @@
 const DimensionsValue* getSingleLeafValue(const DimensionsValue* value);
 DimensionsValue getSingleLeafValue(const DimensionsValue& value);
 
+// Appends the leaf node to the parent tree.
+void appendLeafNodeToParent(const Field& field, const DimensionsValue& value,
+                            DimensionsValue* parentValue);
+
 // Constructs the DimensionsValue protos from the FieldMatcher. Each DimensionsValue proto
 // represents a tree. When the input proto has repeated fields and the input "dimensions" wants
 // "ANY" locations, it will return multiple trees.
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 1a4888c..ae47bd8 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -114,6 +114,9 @@
 
 void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
     flushIfNeededLocked(dumpTimeNs);
+    ProtoOutputStream pbOutput;
+    onDumpReportLocked(dumpTimeNs, &pbOutput);
+    parseProtoOutputStream(pbOutput, report);
 }
 
 void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 08b0981..a0239fc 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -141,6 +141,7 @@
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
+    FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
index 09a43f5..cee9200 100644
--- a/cmds/statsd/src/stats_log_util.h
+++ b/cmds/statsd/src/stats_log_util.h
@@ -43,6 +43,19 @@
 // Helper function to write PulledAtomStats to ProtoOutputStream
 void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats>& pair,
                               util::ProtoOutputStream* protoOutput);
+
+template<class T>
+bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) {
+    std::string pbBytes;
+    auto iter = protoOutput.data();
+    while (iter.readBuffer() != NULL) {
+        size_t toRead = iter.currentToRead();
+         pbBytes.append(reinterpret_cast<const char*>(iter.readBuffer()), toRead);
+        iter.rp()->move(toRead);
+    }
+    return message->ParseFromArray(pbBytes.c_str(), pbBytes.size());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/statsd.rc b/cmds/statsd/statsd.rc
index faccd61..c260ae1 100644
--- a/cmds/statsd/statsd.rc
+++ b/cmds/statsd/statsd.rc
@@ -14,3 +14,10 @@
 
 service statsd /system/bin/statsd
     class main
+    user statsd
+    group statsd log
+
+on post-fs-data
+    # Create directory for statsd
+    mkdir /data/misc/stats-data/ 0770 statsd statsd
+    mkdir /data/misc/stats-service/ 0770 statsd statsd
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp
new file mode 100644
index 0000000..10a6c36
--- /dev/null
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp
@@ -0,0 +1,193 @@
+// 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.
+
+#include <gtest/gtest.h>
+
+#include "src/StatsLogProcessor.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+namespace {
+
+StatsdConfig CreateStatsdConfigForPushedEvent() {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
+    *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher();
+
+    auto atomMatcher = CreateSimpleAtomMatcher("", android::util::APP_START_CHANGED);
+    *config.add_atom_matcher() = atomMatcher;
+
+    auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
+    *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
+        CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ });
+    *config.add_predicate() = isInBackgroundPredicate;
+
+    auto gaugeMetric = config.add_gauge_metric();
+    gaugeMetric->set_id(123456);
+    gaugeMetric->set_what(atomMatcher.id());
+    gaugeMetric->set_condition(isInBackgroundPredicate.id());
+    gaugeMetric->mutable_gauge_fields_filter()->set_include_all(false);
+    auto fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields();
+    fieldMatcher->set_field(android::util::APP_START_CHANGED);
+    fieldMatcher->add_child()->set_field(3);  // type (enum)
+    fieldMatcher->add_child()->set_field(4);  // activity_name(str)
+    fieldMatcher->add_child()->set_field(7);  // activity_start_msec(int64)
+    *gaugeMetric->mutable_dimensions() =
+        CreateDimensions(android::util::APP_START_CHANGED, {1 /* uid field */ });
+    gaugeMetric->set_bucket(ONE_MINUTE);
+
+    auto links = gaugeMetric->add_links();
+    links->set_condition(isInBackgroundPredicate.id());
+    auto dimensionWhat = links->mutable_dimensions_in_what();
+    dimensionWhat->set_field(android::util::APP_START_CHANGED);
+    dimensionWhat->add_child()->set_field(1);  // uid field.
+    auto dimensionCondition = links->mutable_dimensions_in_condition();
+    dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+    dimensionCondition->add_child()->set_field(1);  // uid field.
+    return config;
+}
+
+std::unique_ptr<LogEvent> CreateAppStartChangedEvent(
+    const int uid, const string& pkg_name, AppStartChanged::TransitionType type,
+    const string& activity_name, const string& calling_pkg_name, const bool is_instant_app,
+    int64_t activity_start_msec, uint64_t timestampNs) {
+    auto logEvent = std::make_unique<LogEvent>(
+        android::util::APP_START_CHANGED, timestampNs);
+    logEvent->write(uid);
+    logEvent->write(pkg_name);
+    logEvent->write(type);
+    logEvent->write(activity_name);
+    logEvent->write(calling_pkg_name);
+    logEvent->write(is_instant_app);
+    logEvent->write(activity_start_msec);
+    logEvent->init();
+    return logEvent;
+}
+
+}  // namespace
+
+TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) {
+    auto config = CreateStatsdConfigForPushedEvent();
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs =
+        TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+    int appUid1 = 123;
+    int appUid2 = 456;
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + 15));
+    events.push_back(CreateMoveToForegroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 250));
+    events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 350));
+    events.push_back(CreateMoveToForegroundEvent(
+        appUid1, bucketStartTimeNs + 2 * bucketSizeNs + 100));
+
+
+    events.push_back(CreateAppStartChangedEvent(
+        appUid1, "app1", AppStartChanged::WARM, "activity_name1", "calling_pkg_name1",
+        true /*is_instant_app*/, 101 /*activity_start_msec*/, bucketStartTimeNs + 10));
+    events.push_back(CreateAppStartChangedEvent(
+        appUid1, "app1", AppStartChanged::HOT, "activity_name2", "calling_pkg_name2",
+        true /*is_instant_app*/, 102 /*activity_start_msec*/, bucketStartTimeNs + 20));
+    events.push_back(CreateAppStartChangedEvent(
+        appUid1, "app1", AppStartChanged::COLD, "activity_name3", "calling_pkg_name3",
+        true /*is_instant_app*/, 103 /*activity_start_msec*/, bucketStartTimeNs + 30));
+    events.push_back(CreateAppStartChangedEvent(
+        appUid1, "app1", AppStartChanged::WARM, "activity_name4", "calling_pkg_name4",
+        true /*is_instant_app*/, 104 /*activity_start_msec*/,
+        bucketStartTimeNs + bucketSizeNs + 30));
+    events.push_back(CreateAppStartChangedEvent(
+        appUid1, "app1", AppStartChanged::COLD, "activity_name5", "calling_pkg_name5",
+        true /*is_instant_app*/, 105 /*activity_start_msec*/,
+        bucketStartTimeNs + 2 * bucketSizeNs));
+    events.push_back(CreateAppStartChangedEvent(
+        appUid1, "app1", AppStartChanged::HOT, "activity_name6", "calling_pkg_name6",
+        false /*is_instant_app*/, 106 /*activity_start_msec*/,
+        bucketStartTimeNs + 2 * bucketSizeNs + 10));
+
+    events.push_back(CreateMoveToBackgroundEvent(appUid2, bucketStartTimeNs + bucketSizeNs + 10));
+    events.push_back(CreateAppStartChangedEvent(
+        appUid2, "app2", AppStartChanged::COLD, "activity_name7", "calling_pkg_name7",
+        true /*is_instant_app*/, 201 /*activity_start_msec*/,
+        bucketStartTimeNs + 2 * bucketSizeNs + 10));
+
+    sortLogEventsByTimestamp(&events);
+
+    for (const auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, &reports);
+    EXPECT_EQ(reports.reports_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    EXPECT_EQ(gaugeMetrics.data_size(), 2);
+
+    auto data = gaugeMetrics.data(0);
+    EXPECT_EQ(data.dimension().field(), android::util::APP_START_CHANGED);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1 /* uid field */);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid1);
+    EXPECT_EQ(data.bucket_info_size(), 3);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::HOT);
+    EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name2");
+    EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 102L);
+
+    EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().type(), AppStartChanged::WARM);
+    EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_name(), "activity_name4");
+    EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_start_msec(), 104L);
+
+    EXPECT_EQ(data.bucket_info(2).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(2).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().type(), AppStartChanged::COLD);
+    EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_name(), "activity_name5");
+    EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_start_msec(), 105L);
+
+    data = gaugeMetrics.data(1);
+    EXPECT_EQ(data.dimension().field(), android::util::APP_START_CHANGED);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1 /* uid field */);
+    EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid2);
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs);
+    EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::COLD);
+    EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name7");
+    EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 201L);
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
index 2783356..fcdaafc 100644
--- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
@@ -26,6 +26,8 @@
 
 #ifdef __ANDROID__
 
+namespace {
+
 StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) {
     StatsdConfig config;
     *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
@@ -56,6 +58,8 @@
     return config;
 }
 
+}  // namespace
+
 TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) {
     ConfigKey cfgKey;
     for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) {
@@ -94,6 +98,7 @@
         auto releaseEvent2 = CreateReleaseWakelockEvent(
             attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15);
 
+
         std::vector<std::unique_ptr<LogEvent>> events;
 
         events.push_back(std::move(screenTurnedOnEvent));
@@ -119,18 +124,8 @@
 
         auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
         // Validate dimension value.
-        EXPECT_EQ(data.dimension().field(),
-                  android::util::WAKELOCK_STATE_CHANGED);
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
-        // Attribution field.
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
-        // Uid only.
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
-            .value_tuple().dimensions_value_size(), 1);
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
-            .value_tuple().dimensions_value(0).field(), 1);
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
-            .value_tuple().dimensions_value(0).value_int(), 111);
+        ValidateAttributionUidDimension(
+            data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111);
         // Validate bucket info.
         EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
         data = reports.reports(0).metrics(0).duration_metrics().data(0);
@@ -147,18 +142,8 @@
         EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
         data = reports.reports(0).metrics(0).duration_metrics().data(0);
         // Validate dimension value.
-        EXPECT_EQ(data.dimension().field(),
-                  android::util::WAKELOCK_STATE_CHANGED);
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
-        // Attribution field.
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
-        // Uid only.
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
-            .value_tuple().dimensions_value_size(), 1);
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
-            .value_tuple().dimensions_value(0).field(), 1);
-        EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
-            .value_tuple().dimensions_value(0).value_int(), 111);
+        ValidateAttributionUidDimension(
+            data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111);
         // Two output buckets.
         // The wakelock holding interval in the 1st bucket starts from the screen off event and to
         // the end of the 1st bucket.
@@ -167,6 +152,32 @@
         // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and
         // ends at the second screen on event.
         EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL);
+
+        events.clear();
+        events.push_back(CreateScreenStateChangedEvent(
+            ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 2 * bucketSizeNs + 90));
+        events.push_back(CreateAcquireWakelockEvent(
+            attributions1, "wl3", bucketStartTimeNs + 2 * bucketSizeNs + 100));
+        events.push_back(CreateReleaseWakelockEvent(
+            attributions1, "wl3", bucketStartTimeNs + 5 * bucketSizeNs + 100));
+        sortLogEventsByTimestamp(&events);
+        for (const auto& event : events) {
+            processor->OnLogEvent(event.get());
+        }
+        reports.Clear();
+        processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports);
+        EXPECT_EQ(reports.reports_size(), 1);
+        EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+        EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+        EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6);
+        data = reports.reports(0).metrics(0).duration_metrics().data(0);
+        ValidateAttributionUidDimension(
+            data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111);
+        // The last wakelock holding spans 4 buckets.
+        EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100);
+        EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs);
+        EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs);
+        EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL);
     }
 }
 
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index e788235..718b2e1 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -19,6 +19,14 @@
 namespace os {
 namespace statsd {
 
+AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId(name));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(atomId);
+    return atom_matcher;
+}
+
 AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name,
                                                   WakelockStateChanged::State state) {
     AtomMatcher atom_matcher;
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 1bbbd9a..7eb93b9 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -23,6 +23,9 @@
 namespace os {
 namespace statsd {
 
+// Create AtomMatcher proto to simply match a specific atom type.
+AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
+
 // Create AtomMatcher proto for acquiring wakelock.
 AtomMatcher CreateAcquireWakelockAtomMatcher();
 
diff --git a/core/java/Android.bp b/core/java/Android.bp
index b43cf27..afa08e6 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -3,6 +3,11 @@
     srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"],
 }
 
+filegroup {
+    name: "IDropBoxManagerService.aidl",
+    srcs: ["com/android/internal/os/IDropBoxManagerService.aidl"],
+}
+
 // only used by key_store_service
 cc_library_shared {
     name: "libkeystore_aidl",
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 60a5a11..972ffcb 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -319,4 +319,9 @@
     }
 
     public abstract void registerScreenObserver(ScreenObserver observer);
+
+    /**
+     * Returns if more users can be started without stopping currently running users.
+     */
+    public abstract boolean canStartMoreUsers();
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index de346f3..e610ac4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1768,7 +1768,14 @@
                             (String[]) ((SomeArgs) msg.obj).arg2);
                     break;
                 case EXECUTE_TRANSACTION:
-                    mTransactionExecutor.execute(((ClientTransaction) msg.obj));
+                    final ClientTransaction transaction = (ClientTransaction) msg.obj;
+                    mTransactionExecutor.execute(transaction);
+                    if (isSystem()) {
+                        // Client transactions inside system process are recycled on the client side
+                        // instead of ClientLifecycleManager to avoid being cleared before this
+                        // message is handled.
+                        transaction.recycle();
+                    }
                     break;
             }
             Object obj = msg.obj;
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 82f2bac..4048e65 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2738,6 +2738,24 @@
     }
 
     @Override
+    public CharSequence getHarmfulAppWarning(String packageName) {
+        try {
+            return mPM.getHarmfulAppWarning(packageName, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void setHarmfulAppWarning(String packageName, CharSequence warning) {
+        try {
+            mPM.setHarmfulAppWarning(packageName, warning, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
     public ArtManager getArtManager() {
         synchronized (mLock) {
             if (mArtManager == null) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index a9e633f..696899f 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -620,8 +620,9 @@
     boolean updateDisplayOverrideConfiguration(in Configuration values, int displayId);
     void moveStackToDisplay(int stackId, int displayId);
     boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
-                                in IBinder activityToken, int flags);
-    void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
+            in IBinder activityToken, int flags);
+    void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback,
+            in CharSequence message);
     int restartUserInBackground(int userId);
 
     /** Cancels the window transitions for the given task. */
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index af536f6..024dbcb 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -477,6 +477,39 @@
      */
     public void requestDismissKeyguard(@NonNull Activity activity,
             @Nullable KeyguardDismissCallback callback) {
+        requestDismissKeyguard(activity, null /* message */, callback);
+    }
+
+    /**
+     * If the device is currently locked (see {@link #isKeyguardLocked()}, requests the Keyguard to
+     * be dismissed.
+     * <p>
+     * If the Keyguard is not secure or the device is currently in a trusted state, calling this
+     * method will immediately dismiss the Keyguard without any user interaction.
+     * <p>
+     * If the Keyguard is secure and the device is not in a trusted state, this will bring up the
+     * UI so the user can enter their credentials.
+     * <p>
+     * If the value set for the {@link Activity} attr {@link android.R.attr#turnScreenOn} is true,
+     * the screen will turn on when the keyguard is dismissed.
+     *
+     * @param activity The activity requesting the dismissal. The activity must be either visible
+     *                 by using {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} or must be in a state in
+     *                 which it would be visible if Keyguard would not be hiding it. If that's not
+     *                 the case, the request will fail immediately and
+     *                 {@link KeyguardDismissCallback#onDismissError} will be invoked.
+     * @param message  A message that will be shown in the keyguard explaining why the user
+     *                 would want to dismiss it.
+     * @param callback The callback to be called if the request to dismiss Keyguard was successful
+     *                 or {@code null} if the caller isn't interested in knowing the result. The
+     *                 callback will not be invoked if the activity was destroyed before the
+     *                 callback was received.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SHOW_KEYGUARD_MESSAGE)
+    @SystemApi
+    public void requestDismissKeyguard(@NonNull Activity activity, @Nullable CharSequence message,
+            @Nullable KeyguardDismissCallback callback) {
         try {
             mAm.dismissKeyguard(activity.getActivityToken(), new IKeyguardDismissCallback.Stub() {
                 @Override
@@ -499,7 +532,7 @@
                         activity.mHandler.post(callback::onDismissCancelled);
                     }
                 }
-            });
+            }, message);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a383604..75dc571 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1268,10 +1268,67 @@
          */
         private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
 
+        /**
+         * {@link }: No semantic action defined.
+         */
+        public static final int SEMANTIC_ACTION_NONE = 0;
+
+        /**
+         * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies
+         * may be appropriate.
+         */
+        public static final int SEMANTIC_ACTION_REPLY = 1;
+
+        /**
+         * {@code SemanticAction}: Mark content as read.
+         */
+        public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
+
+        /**
+         * {@code SemanticAction}: Mark content as unread.
+         */
+        public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
+
+        /**
+         * {@code SemanticAction}: Delete the content associated with the notification. This
+         * could mean deleting an email, message, etc.
+         */
+        public static final int SEMANTIC_ACTION_DELETE = 4;
+
+        /**
+         * {@code SemanticAction}: Archive the content associated with the notification. This
+         * could mean archiving an email, message, etc.
+         */
+        public static final int SEMANTIC_ACTION_ARCHIVE = 5;
+
+        /**
+         * {@code SemanticAction}: Mute the content associated with the notification. This could
+         * mean silencing a conversation or currently playing media.
+         */
+        public static final int SEMANTIC_ACTION_MUTE = 6;
+
+        /**
+         * {@code SemanticAction}: Unmute the content associated with the notification. This could
+         * mean un-silencing a conversation or currently playing media.
+         */
+        public static final int SEMANTIC_ACTION_UNMUTE = 7;
+
+        /**
+         * {@code SemanticAction}: Mark content with a thumbs up.
+         */
+        public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
+
+        /**
+         * {@code SemanticAction}: Mark content with a thumbs down.
+         */
+        public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
+
+
         private final Bundle mExtras;
         private Icon mIcon;
         private final RemoteInput[] mRemoteInputs;
         private boolean mAllowGeneratedReplies = true;
+        private final @SemanticAction int mSemanticAction;
 
         /**
          * Small icon representing the action.
@@ -1306,6 +1363,7 @@
             mExtras = Bundle.setDefusable(in.readBundle(), true);
             mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
             mAllowGeneratedReplies = in.readInt() == 1;
+            mSemanticAction = in.readInt();
         }
 
         /**
@@ -1313,12 +1371,14 @@
          */
         @Deprecated
         public Action(int icon, CharSequence title, PendingIntent intent) {
-            this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true);
+            this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
+                    SEMANTIC_ACTION_NONE);
         }
 
         /** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
         private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
-                RemoteInput[] remoteInputs, boolean allowGeneratedReplies) {
+                RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
+                       @SemanticAction int semanticAction) {
             this.mIcon = icon;
             if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
                 this.icon = icon.getResId();
@@ -1328,6 +1388,7 @@
             this.mExtras = extras != null ? extras : new Bundle();
             this.mRemoteInputs = remoteInputs;
             this.mAllowGeneratedReplies = allowGeneratedReplies;
+            this.mSemanticAction = semanticAction;
         }
 
         /**
@@ -1366,6 +1427,15 @@
         }
 
         /**
+         * Returns the {@code SemanticAction} associated with this {@link Action}. A
+         * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
+         * (eg. reply, mark as read, delete, etc).
+         */
+        public @SemanticAction int getSemanticAction() {
+            return mSemanticAction;
+        }
+
+        /**
          * Get the list of inputs to be collected from the user that ONLY accept data when this
          * action is sent. These remote inputs are guaranteed to return true on a call to
          * {@link RemoteInput#isDataOnly}.
@@ -1389,6 +1459,7 @@
             private boolean mAllowGeneratedReplies = true;
             private final Bundle mExtras;
             private ArrayList<RemoteInput> mRemoteInputs;
+            private @SemanticAction int mSemanticAction;
 
             /**
              * Construct a new builder for {@link Action} object.
@@ -1408,7 +1479,7 @@
              * @param intent the {@link PendingIntent} to fire when users trigger this action
              */
             public Builder(Icon icon, CharSequence title, PendingIntent intent) {
-                this(icon, title, intent, new Bundle(), null, true);
+                this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE);
             }
 
             /**
@@ -1419,11 +1490,12 @@
             public Builder(Action action) {
                 this(action.getIcon(), action.title, action.actionIntent,
                         new Bundle(action.mExtras), action.getRemoteInputs(),
-                        action.getAllowGeneratedReplies());
+                        action.getAllowGeneratedReplies(), action.getSemanticAction());
             }
 
             private Builder(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
-                    RemoteInput[] remoteInputs, boolean allowGeneratedReplies) {
+                    RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
+                            @SemanticAction int semanticAction) {
                 mIcon = icon;
                 mTitle = title;
                 mIntent = intent;
@@ -1433,6 +1505,7 @@
                     Collections.addAll(mRemoteInputs, remoteInputs);
                 }
                 mAllowGeneratedReplies = allowGeneratedReplies;
+                mSemanticAction = semanticAction;
             }
 
             /**
@@ -1488,6 +1561,19 @@
             }
 
             /**
+             * Sets the {@code SemanticAction} for this {@link Action}. A
+             * {@code SemanticAction} denotes what an {@link Action}'s
+             * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc).
+             * @param semanticAction a SemanticAction defined within {@link Action} with
+             * {@code SEMANTIC_ACTION_} prefixes
+             * @return this object for method chaining
+             */
+            public Builder setSemanticAction(@SemanticAction int semanticAction) {
+                mSemanticAction = semanticAction;
+                return this;
+            }
+
+            /**
              * Apply an extender to this action builder. Extenders may be used to add
              * metadata or change options on this builder.
              */
@@ -1528,7 +1614,7 @@
                 RemoteInput[] textInputsArr = textInputs.isEmpty()
                         ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
                 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
-                        mAllowGeneratedReplies);
+                        mAllowGeneratedReplies, mSemanticAction);
             }
         }
 
@@ -1540,12 +1626,15 @@
                     actionIntent, // safe to alias
                     mExtras == null ? new Bundle() : new Bundle(mExtras),
                     getRemoteInputs(),
-                    getAllowGeneratedReplies());
+                    getAllowGeneratedReplies(),
+                    getSemanticAction());
         }
+
         @Override
         public int describeContents() {
             return 0;
         }
+
         @Override
         public void writeToParcel(Parcel out, int flags) {
             final Icon ic = getIcon();
@@ -1565,7 +1654,9 @@
             out.writeBundle(mExtras);
             out.writeTypedArray(mRemoteInputs, flags);
             out.writeInt(mAllowGeneratedReplies ? 1 : 0);
+            out.writeInt(mSemanticAction);
         }
+
         public static final Parcelable.Creator<Action> CREATOR =
                 new Parcelable.Creator<Action>() {
             public Action createFromParcel(Parcel in) {
@@ -1827,6 +1918,29 @@
                 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
             }
         }
+
+        /**
+         * Provides meaning to an {@link Action} that hints at what the associated
+         * {@link PendingIntent} will do. For example, an {@link Action} with a
+         * {@link PendingIntent} that replies to a text message notification may have the
+         * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it.
+         *
+         * @hide
+         */
+        @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = {
+                SEMANTIC_ACTION_NONE,
+                SEMANTIC_ACTION_REPLY,
+                SEMANTIC_ACTION_MARK_AS_READ,
+                SEMANTIC_ACTION_MARK_AS_UNREAD,
+                SEMANTIC_ACTION_DELETE,
+                SEMANTIC_ACTION_ARCHIVE,
+                SEMANTIC_ACTION_MUTE,
+                SEMANTIC_ACTION_UNMUTE,
+                SEMANTIC_ACTION_THUMBS_UP,
+                SEMANTIC_ACTION_THUMBS_DOWN
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface SemanticAction {}
     }
 
     /**
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 8c47598..6ac15a5 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -71,6 +71,8 @@
 
     @GuardedBy("mLock")
     private Map<String, Object> mMap;
+    @GuardedBy("mLock")
+    private Throwable mThrowable;
 
     @GuardedBy("mLock")
     private int mDiskWritesInFlight = 0;
@@ -107,6 +109,7 @@
         mMode = mode;
         mLoaded = false;
         mMap = null;
+        mThrowable = null;
         startLoadFromDisk();
     }
 
@@ -139,13 +142,14 @@
 
         Map<String, Object> map = null;
         StructStat stat = null;
+        Throwable thrown = null;
         try {
             stat = Os.stat(mFile.getPath());
             if (mFile.canRead()) {
                 BufferedInputStream str = null;
                 try {
                     str = new BufferedInputStream(
-                            new FileInputStream(mFile), 16*1024);
+                            new FileInputStream(mFile), 16 * 1024);
                     map = (Map<String, Object>) XmlUtils.readMapXml(str);
                 } catch (Exception e) {
                     Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
@@ -154,19 +158,36 @@
                 }
             }
         } catch (ErrnoException e) {
-            /* ignore */
+            // An errno exception means the stat failed. Treat as empty/non-existing by
+            // ignoring.
+        } catch (Throwable t) {
+            thrown = t;
         }
 
         synchronized (mLock) {
             mLoaded = true;
-            if (map != null) {
-                mMap = map;
-                mStatTimestamp = stat.st_mtim;
-                mStatSize = stat.st_size;
-            } else {
-                mMap = new HashMap<>();
+            mThrowable = thrown;
+
+            // It's important that we always signal waiters, even if we'll make
+            // them fail with an exception. The try-finally is pretty wide, but
+            // better safe than sorry.
+            try {
+                if (thrown == null) {
+                    if (map != null) {
+                        mMap = map;
+                        mStatTimestamp = stat.st_mtim;
+                        mStatSize = stat.st_size;
+                    } else {
+                        mMap = new HashMap<>();
+                    }
+                }
+                // In case of a thrown exception, we retain the old map. That allows
+                // any open editors to commit and store updates.
+            } catch (Throwable t) {
+                mThrowable = t;
+            } finally {
+                mLock.notifyAll();
             }
-            mLock.notifyAll();
         }
     }
 
@@ -226,6 +247,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void awaitLoadedLocked() {
         if (!mLoaded) {
             // Raise an explicit StrictMode onReadFromDisk for this
@@ -239,6 +261,9 @@
             } catch (InterruptedException unused) {
             }
         }
+        if (mThrowable != null) {
+            throw new IllegalStateException(mThrowable);
+        }
     }
 
     @Override
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 6eb4783..6eafcc4 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -136,6 +136,7 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.euicc.EuiccCardManager;
 import android.telephony.euicc.EuiccManager;
 import android.util.Log;
 import android.util.StatsManager;
@@ -519,6 +520,13 @@
                 return new EuiccManager(ctx.getOuterContext());
             }});
 
+        registerService(Context.EUICC_CARD_SERVICE, EuiccCardManager.class,
+                new CachedServiceFetcher<EuiccCardManager>() {
+                    @Override
+                    public EuiccCardManager createService(ContextImpl ctx) {
+                        return new EuiccCardManager(ctx.getOuterContext());
+                    }});
+
         registerService(Context.UI_MODE_SERVICE, UiModeManager.class,
                 new CachedServiceFetcher<UiModeManager>() {
             @Override
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 8f01685..ba39740 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -24,7 +24,6 @@
 import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -47,10 +46,14 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
+
+import com.android.internal.util.CollectionUtils;
+
 import libcore.io.IoUtils;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
 
@@ -580,6 +583,8 @@
         // Execute the command *without* the lock being held.
         command.run();
 
+        List<AccessibilityEvent> eventsReceived = Collections.emptyList();
+
         // Acquire the lock and wait for the event.
         try {
             // Wait for the event.
@@ -600,14 +605,14 @@
                     if (filter.accept(event)) {
                         return event;
                     }
-                    event.recycle();
+                    eventsReceived = CollectionUtils.add(eventsReceived, event);
                 }
                 // Check if timed out and if not wait.
                 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
                 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
                 if (remainingTimeMillis <= 0) {
                     throw new TimeoutException("Expected event not received within: "
-                            + timeoutMillis + " ms.");
+                            + timeoutMillis + " ms, among " + eventsReceived);
                 }
                 synchronized (mLock) {
                     if (mEventQueue.isEmpty()) {
@@ -620,6 +625,10 @@
                 }
             }
         } finally {
+            for (int i = 0; i < CollectionUtils.size(eventsReceived); i++) {
+                AccessibilityEvent event = eventsReceived.get(i);
+                event.recycle();
+            }
             synchronized (mLock) {
                 mWaitingForEventDelivery = false;
                 mEventQueue.clear();
diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java
index f06a925..d511c57 100644
--- a/core/java/android/app/admin/ConnectEvent.java
+++ b/core/java/android/app/admin/ConnectEvent.java
@@ -68,7 +68,7 @@
 
     @Override
     public String toString() {
-        return String.format("ConnectEvent(%s, %d, %d, %s)", mIpAddress, mPort, mTimestamp,
+        return String.format("ConnectEvent(%d, %s, %d, %d, %s)", mId, mIpAddress, mPort, mTimestamp,
                 mPackageName);
     }
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ab85fdc..e334aab 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1254,6 +1254,26 @@
             = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
 
     /**
+     * Broadcast action to notify ManagedProvisioning that
+     * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has changed.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DATA_SHARING_RESTRICTION_CHANGED =
+            "android.app.action.DATA_SHARING_RESTRICTION_CHANGED";
+
+    /**
+     * Broadcast action from ManagedProvisioning to notify that the latest change to
+     * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has been successfully
+     * applied (cross profile intent filters updated). Only usesd for CTS tests.
+     * @hide
+     */
+    @TestApi
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED =
+            "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
+
+    /**
      * Permission policy to prompt user for new permission requests for runtime permissions.
      * Already granted or denied permissions are not affected by this.
      */
@@ -6057,6 +6077,13 @@
      * Called by a profile owner of a managed profile to remove the cross-profile intent filters
      * that go from the managed profile to the parent, or from the parent to the managed profile.
      * Only removes those that have been set by the profile owner.
+     * <p>
+     * <em>Note</em>: A list of default cross profile intent filters are set up by the system when
+     * the profile is created, some of them ensure the proper functioning of the profile, while
+     * others enable sharing of data from the parent to the managed profile for user convenience.
+     * These default intent filters are not cleared when this API is called. If the default cross
+     * profile data sharing is not desired, they can be disabled with
+     * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE}.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
@@ -6479,12 +6506,6 @@
     public static final int MAKE_USER_DEMO = 0x0004;
 
     /**
-     * Flag used by {@link #createAndManageUser} to specify that the newly created user should be
-     * started in the background as part of the user creation.
-     */
-    public static final int START_USER_IN_BACKGROUND = 0x0008;
-
-    /**
      * Flag used by {@link #createAndManageUser} to specify that the newly created user should skip
      * the disabling of system apps during provisioning.
      */
@@ -6497,7 +6518,6 @@
             SKIP_SETUP_WIZARD,
             MAKE_USER_EPHEMERAL,
             MAKE_USER_DEMO,
-            START_USER_IN_BACKGROUND,
             LEAVE_ALL_SYSTEM_APPS_ENABLED
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -6526,7 +6546,8 @@
      *            IllegalArgumentException is thrown.
      * @param adminExtras Extras that will be passed to onEnable of the admin receiver on the new
      *            user.
-     * @param flags {@link #SKIP_SETUP_WIZARD} is supported.
+     * @param flags {@link #SKIP_SETUP_WIZARD}, {@link #MAKE_USER_EPHEMERAL} and
+     *        {@link #LEAVE_ALL_SYSTEM_APPS_ENABLED} are supported.
      * @see UserHandle
      * @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the
      *         user could not be created.
@@ -6545,8 +6566,8 @@
     }
 
     /**
-     * Called by a device owner to remove a user and all associated data. The primary user can not
-     * be removed.
+     * Called by a device owner to remove a user/profile and all associated data. The primary user
+     * can not be removed.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param userHandle the user to remove.
@@ -6563,14 +6584,14 @@
     }
 
     /**
-     * Called by a device owner to switch the specified user to the foreground.
-     * <p> This cannot be used to switch to a managed profile.
+     * Called by a device owner to switch the specified secondary user to the foreground.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param userHandle the user to switch to; null will switch to primary.
      * @return {@code true} if the switch was successful, {@code false} otherwise.
      * @throws SecurityException if {@code admin} is not a device owner.
      * @see Intent#ACTION_USER_FOREGROUND
+     * @see #getSecondaryUsers(ComponentName)
      */
     public boolean switchUser(@NonNull ComponentName admin, @Nullable UserHandle userHandle) {
         throwIfParentInstance("switchUser");
@@ -6582,13 +6603,32 @@
     }
 
     /**
+     * Called by a device owner to start the specified secondary user in background.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param userHandle the user to be stopped.
+     * @return {@code true} if the user can be started, {@code false} otherwise.
+     * @throws SecurityException if {@code admin} is not a device owner.
+     * @see #getSecondaryUsers(ComponentName)
+     */
+    public boolean startUserInBackground(
+            @NonNull ComponentName admin, @NonNull UserHandle userHandle) {
+        throwIfParentInstance("startUserInBackground");
+        try {
+            return mService.startUserInBackground(admin, userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Called by a device owner to stop the specified secondary user.
-     * <p> This cannot be used to stop the primary user or a managed profile.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param userHandle the user to be stopped.
      * @return {@code true} if the user can be stopped, {@code false} otherwise.
      * @throws SecurityException if {@code admin} is not a device owner.
+     * @see #getSecondaryUsers(ComponentName)
      */
     public boolean stopUser(@NonNull ComponentName admin, @NonNull UserHandle userHandle) {
         throwIfParentInstance("stopUser");
@@ -6600,14 +6640,13 @@
     }
 
     /**
-     * Called by a profile owner that is affiliated with the device to stop the calling user
-     * and switch back to primary.
-     * <p> This has no effect when called on a managed profile.
+     * Called by a profile owner of secondary user that is affiliated with the device to stop the
+     * calling user and switch back to primary.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @return {@code true} if the exit was successful, {@code false} otherwise.
      * @throws SecurityException if {@code admin} is not a profile owner affiliated with the device.
-     * @see #isAffiliatedUser
+     * @see #getSecondaryUsers(ComponentName)
      */
     public boolean logoutUser(@NonNull ComponentName admin) {
         throwIfParentInstance("logoutUser");
@@ -6619,17 +6658,18 @@
     }
 
     /**
-     * Called by a device owner to list all secondary users on the device, excluding managed
-     * profiles.
+     * Called by a device owner to list all secondary users on the device. Managed profiles are not
+     * considered as secondary users.
      * <p> Used for various user management APIs, including {@link #switchUser}, {@link #removeUser}
      * and {@link #stopUser}.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @return list of other {@link UserHandle}s on the device.
      * @throws SecurityException if {@code admin} is not a device owner.
-     * @see #switchUser
-     * @see #removeUser
-     * @see #stopUser
+     * @see #removeUser(ComponentName, UserHandle)
+     * @see #switchUser(ComponentName, UserHandle)
+     * @see #startUserInBackground(ComponentName, UserHandle)
+     * @see #stopUser(ComponentName, UserHandle)
      */
     public List<UserHandle> getSecondaryUsers(@NonNull ComponentName admin) {
         throwIfParentInstance("getSecondaryUsers");
diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java
index 4ddf13e..a2d704b 100644
--- a/core/java/android/app/admin/DnsEvent.java
+++ b/core/java/android/app/admin/DnsEvent.java
@@ -96,7 +96,7 @@
 
     @Override
     public String toString() {
-        return String.format("DnsEvent(%s, %s, %d, %d, %s)", mHostname,
+        return String.format("DnsEvent(%d, %s, %s, %d, %d, %s)", mId, mHostname,
                 (mIpAddresses == null) ? "NONE" : String.join(" ", mIpAddresses),
                 mIpAddressesCount, mTimestamp, mPackageName);
     }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 1d8ddee..7154053 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -226,6 +226,7 @@
     UserHandle createAndManageUser(in ComponentName who, in String name, in ComponentName profileOwner, in PersistableBundle adminExtras, in int flags);
     boolean removeUser(in ComponentName who, in UserHandle userHandle);
     boolean switchUser(in ComponentName who, in UserHandle userHandle);
+    boolean startUserInBackground(in ComponentName who, in UserHandle userHandle);
     boolean stopUser(in ComponentName who, in UserHandle userHandle);
     boolean logoutUser(in ComponentName who);
     List<UserHandle> getSecondaryUsers(in ComponentName who);
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index da81d19..3558e34 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -55,6 +55,22 @@
     // Transport should ignore its own moratoriums for call with this flag set.
     public static final int FLAG_USER_INITIATED = 1;
 
+    /**
+     * For key value backup, indicates that the backup data is a diff from a previous backup. The
+     * transport must apply this diff to an existing backup to build the new backup set.
+     *
+     * @hide
+     */
+    public static final int FLAG_INCREMENTAL = 1 << 1;
+
+    /**
+     * For key value backup, indicates that the backup data is a complete set, not a diff from a
+     * previous backup. The transport should clear any previous backup when storing this backup.
+     *
+     * @hide
+     */
+    public static final int FLAG_NON_INCREMENTAL = 1 << 2;
+
     IBackupTransport mBinderImpl = new TransportImpl();
 
     public IBinder getBinder() {
@@ -231,12 +247,18 @@
      * {@link #TRANSPORT_OK}, {@link #finishBackup} will then be called to ensure the data
      * is sent and recorded successfully.
      *
+     * If the backup data is a diff against the previous backup then the flag {@link
+     * BackupTransport#FLAG_INCREMENTAL} will be set. Otherwise, if the data is a complete backup
+     * set then {@link BackupTransport#FLAG_NON_INCREMENTAL} will be set. Before P neither flag will
+     * be set regardless of whether the backup is incremental or not.
+     *
      * @param packageInfo The identity of the application whose data is being backed up.
      *   This specifically includes the signature list for the package.
      * @param inFd Descriptor of file with data that resulted from invoking the application's
      *   BackupService.doBackup() method.  This may be a pipe rather than a file on
      *   persistent media, so it may not be seekable.
-     * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0.
+     * @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link
+     *   BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0.
      * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
      *  {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this
      *  specific package, but allow others to proceed),
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 7703c6b..08ad2f0 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -56,6 +56,11 @@
     /** Target client activity. Might be null if the entire transaction is targeting an app. */
     private IBinder mActivityToken;
 
+    /** Get the target client of the transaction. */
+    public IApplicationThread getClient() {
+        return mClient;
+    }
+
     /**
      * Add a message to the end of the sequence of callbacks.
      * @param activityCallback A single message that can contain a lifecycle request/callback.
diff --git a/core/java/android/app/servertransaction/ObjectPool.java b/core/java/android/app/servertransaction/ObjectPool.java
index 9812125..2fec30a 100644
--- a/core/java/android/app/servertransaction/ObjectPool.java
+++ b/core/java/android/app/servertransaction/ObjectPool.java
@@ -16,8 +16,8 @@
 
 package android.app.servertransaction;
 
+import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.Map;
 
 /**
@@ -27,7 +27,7 @@
 class ObjectPool {
 
     private static final Object sPoolSync = new Object();
-    private static final Map<Class, LinkedList<? extends ObjectPoolItem>> sPoolMap =
+    private static final Map<Class, ArrayList<? extends ObjectPoolItem>> sPoolMap =
             new HashMap<>();
 
     private static final int MAX_POOL_SIZE = 50;
@@ -40,9 +40,9 @@
     public static <T extends ObjectPoolItem> T obtain(Class<T> itemClass) {
         synchronized (sPoolSync) {
             @SuppressWarnings("unchecked")
-            LinkedList<T> itemPool = (LinkedList<T>) sPoolMap.get(itemClass);
+            final ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(itemClass);
             if (itemPool != null && !itemPool.isEmpty()) {
-                return itemPool.poll();
+                return itemPool.remove(itemPool.size() - 1);
             }
             return null;
         }
@@ -56,16 +56,20 @@
     public static <T extends ObjectPoolItem> void recycle(T item) {
         synchronized (sPoolSync) {
             @SuppressWarnings("unchecked")
-            LinkedList<T> itemPool = (LinkedList<T>) sPoolMap.get(item.getClass());
+            ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(item.getClass());
             if (itemPool == null) {
-                itemPool = new LinkedList<>();
+                itemPool = new ArrayList<>();
                 sPoolMap.put(item.getClass(), itemPool);
             }
-            if (itemPool.contains(item)) {
-                throw new IllegalStateException("Trying to recycle already recycled item");
+            // Check if the item is already in the pool
+            final int size = itemPool.size();
+            for (int i = 0; i < size; i++) {
+                if (itemPool.get(i) == item) {
+                    throw new IllegalStateException("Trying to recycle already recycled item");
+                }
             }
 
-            if (itemPool.size() < MAX_POOL_SIZE) {
+            if (size < MAX_POOL_SIZE) {
                 itemPool.add(item);
             }
         }
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 93e14de..5cd0981 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -22,6 +22,7 @@
 import android.content.res.Configuration;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * UsageStatsManager local system service interface.
@@ -159,6 +160,22 @@
     public abstract void applyRestoredPayload(@UserIdInt int userId, String key, byte[] payload);
 
     /**
+     * Called by DevicePolicyManagerService to inform that a new admin has been added.
+     *
+     * @param packageName the package in which the admin component is part of.
+     * @param userId the userId in which the admin has been added.
+     */
+    public abstract void onActiveAdminAdded(String packageName, int userId);
+
+    /**
+     * Called by DevicePolicyManagerService to inform about the active admins in an user.
+     *
+     * @param adminApps the set of active admins in {@param userId} or null if there are none.
+     * @param userId the userId to which the admin apps belong.
+     */
+    public abstract void setActiveAdminApps(Set<String> adminApps, int userId);
+
+    /**
      * Return usage stats.
      *
      * @param obfuscateInstantApps whether instant app package names need to be obfuscated in the
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index ad7a93c..9b736b7 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -618,6 +618,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int ACCESS_UNKNOWN = 0;
 
     /**
@@ -626,6 +627,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int ACCESS_ALLOWED = 1;
 
     /**
@@ -634,6 +636,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int ACCESS_REJECTED = 2;
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index c94540a..a68f485 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -556,8 +556,8 @@
      * Set priority of the profile
      *
      * <p> The device should already be paired.
-     * Priority can be one of {@link #PRIORITY_ON} or
-     * {@link #PRIORITY_OFF},
+     * Priority can be one of {@link BluetoothProfile#PRIORITY_ON} or
+     * {@link BluetoothProfile#PRIORITY_OFF},
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
      * permission.
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 41cf809..0e2263f 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -19,6 +19,7 @@
 
 import android.Manifest;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 
 import java.util.List;
 
@@ -185,6 +186,7 @@
      *
      * @hide
      **/
+    @SystemApi
     public static final int PRIORITY_ON = 100;
 
     /**
@@ -193,6 +195,7 @@
      *
      * @hide
      **/
+    @SystemApi
     public static final int PRIORITY_OFF = 0;
 
     /**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1a0d5aa..f69e764 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3660,6 +3660,17 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.telephony.euicc.EuiccCardManager} to access the device eUICC (embedded SIM).
+     *
+     * @see #getSystemService(String)
+     * @see android.telephony.euicc.EuiccCardManager
+     * TODO(b/35851809): Make this a SystemApi.
+     * @hide
+     */
+    public static final String EUICC_CARD_SERVICE = "euicc_card_service";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.content.ClipboardManager} for accessing and modifying
      * the contents of the global clipboard.
      *
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index e319750..cce6b84 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -652,4 +652,8 @@
     String getInstantAppAndroidId(String packageName, int userId);
 
     IArtManager getArtManager();
+
+    void setHarmfulAppWarning(String packageName, CharSequence warning, int userId);
+
+    CharSequence getHarmfulAppWarning(String packageName, int userId);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6cd4285..deb8dfb 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -787,7 +787,8 @@
 
     /**
      * Flag parameter for {@link #installPackage} to indicate that this package is an
-     * upgrade to a package that refers to the SDK via release letter.
+     * upgrade to a package that refers to the SDK via release letter or is targeting an SDK via
+     * release letter that the current build does not support.
      *
      * @hide
      */
@@ -5862,4 +5863,37 @@
     public @NonNull ArtManager getArtManager() {
         throw new UnsupportedOperationException("getArtManager not implemented in subclass");
     }
+
+    /**
+     * Sets or clears the harmful app warning details for the given app.
+     *
+     * When set, any attempt to launch an activity in this package will be intercepted and a
+     * warning dialog will be shown to the user instead, with the given warning. The user
+     * will have the option to proceed with the activity launch, or to uninstall the application.
+     *
+     * @param packageName The full name of the package to warn on.
+     * @param warning A warning string to display to the user describing the threat posed by the
+     *                application, or null to clear the warning.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SET_HARMFUL_APP_WARNINGS)
+    @SystemApi
+    public void setHarmfulAppWarning(@NonNull String packageName, @Nullable CharSequence warning) {
+        throw new UnsupportedOperationException("setHarmfulAppWarning not implemented in subclass");
+    }
+
+    /**
+     * Returns the harmful app warning string for the given app, or null if there is none set.
+     *
+     * @param packageName The full name of the desired package.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SET_HARMFUL_APP_WARNINGS)
+    @Nullable
+    @SystemApi
+    public CharSequence getHarmfulAppWarning(@NonNull String packageName) {
+        throw new UnsupportedOperationException("getHarmfulAppWarning not implemented in subclass");
+    }
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 77eb57f2..a18f22e 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -35,7 +35,6 @@
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
 import static android.os.Build.VERSION_CODES.O;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
@@ -85,7 +84,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TypedValue;
-import android.util.apk.ApkSignatureSchemeV2Verifier;
 import android.util.apk.ApkSignatureVerifier;
 import android.view.Gravity;
 
@@ -112,8 +110,7 @@
 import java.security.KeyFactory;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
 import java.security.spec.EncodedKeySpec;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.X509EncodedKeySpec;
@@ -158,10 +155,6 @@
     private static final boolean MULTI_PACKAGE_APK_ENABLED = Build.IS_DEBUGGABLE &&
             SystemProperties.getBoolean(PROPERTY_CHILD_PACKAGES_ENABLED, false);
 
-    public static final int APK_SIGNING_UNKNOWN = 0;
-    public static final int APK_SIGNING_V1 = 1;
-    public static final int APK_SIGNING_V2 = 2;
-
     private static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f;
 
     // TODO: switch outError users to PackageParserException
@@ -477,8 +470,7 @@
         public final int revisionCode;
         public final int installLocation;
         public final VerifierInfo[] verifiers;
-        public final Signature[] signatures;
-        public final Certificate[][] certificates;
+        public final SigningDetails signingDetails;
         public final boolean coreApp;
         public final boolean debuggable;
         public final boolean multiArch;
@@ -486,10 +478,11 @@
         public final boolean extractNativeLibs;
         public final boolean isolatedSplits;
 
-        public ApkLite(String codePath, String packageName, String splitName, boolean isFeatureSplit,
+        public ApkLite(String codePath, String packageName, String splitName,
+                boolean isFeatureSplit,
                 String configForSplit, String usesSplitName, int versionCode, int versionCodeMajor,
                 int revisionCode, int installLocation, List<VerifierInfo> verifiers,
-                Signature[] signatures, Certificate[][] certificates, boolean coreApp,
+                SigningDetails signingDetails, boolean coreApp,
                 boolean debuggable, boolean multiArch, boolean use32bitAbi,
                 boolean extractNativeLibs, boolean isolatedSplits) {
             this.codePath = codePath;
@@ -502,9 +495,8 @@
             this.versionCodeMajor = versionCodeMajor;
             this.revisionCode = revisionCode;
             this.installLocation = installLocation;
+            this.signingDetails = signingDetails;
             this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
-            this.signatures = signatures;
-            this.certificates = certificates;
             this.coreApp = coreApp;
             this.debuggable = debuggable;
             this.multiArch = multiArch;
@@ -807,10 +799,10 @@
             }
         }
         if ((flags&PackageManager.GET_SIGNATURES) != 0) {
-           int N = (p.mSignatures != null) ? p.mSignatures.length : 0;
-           if (N > 0) {
-                pi.signatures = new Signature[N];
-                System.arraycopy(p.mSignatures, 0, pi.signatures, 0, N);
+            if (p.mSigningDetails.hasSignatures()) {
+                int numberOfSigs = p.mSigningDetails.signatures.length;
+                pi.signatures = new Signature[numberOfSigs];
+                System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs);
             }
         }
         return pi;
@@ -818,15 +810,14 @@
 
     public static final int PARSE_MUST_BE_APK = 1 << 0;
     public static final int PARSE_IGNORE_PROCESSES = 1 << 1;
+    /** @deprecated forward lock no longer functional. remove. */
+    @Deprecated
     public static final int PARSE_FORWARD_LOCK = 1 << 2;
     public static final int PARSE_EXTERNAL_STORAGE = 1 << 3;
     public static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
     public static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
     public static final int PARSE_ENFORCE_CODE = 1 << 6;
     public static final int PARSE_FORCE_SDK = 1 << 7;
-    /** @deprecated remove when fixing b/68860689 */
-    @Deprecated
-    public static final int PARSE_IS_EPHEMERAL = 1 << 8;
     public static final int PARSE_CHATTY = 1 << 31;
 
     @IntDef(flag = true, prefix = { "PARSE_" }, value = {
@@ -837,7 +828,6 @@
             PARSE_FORCE_SDK,
             PARSE_FORWARD_LOCK,
             PARSE_IGNORE_PROCESSES,
-            PARSE_IS_EPHEMERAL,
             PARSE_IS_SYSTEM_DIR,
             PARSE_MUST_BE_APK,
     })
@@ -1349,7 +1339,7 @@
             pkg.setVolumeUuid(volumeUuid);
             pkg.setApplicationVolumeUuid(volumeUuid);
             pkg.setBaseCodePath(apkPath);
-            pkg.setSignatures(null);
+            pkg.setSigningDetails(SigningDetails.UNKNOWN);
 
             return pkg;
 
@@ -1469,57 +1459,19 @@
         return pkg;
     }
 
-    public static int getApkSigningVersion(Package pkg) {
-        try {
-            if (ApkSignatureSchemeV2Verifier.hasSignature(pkg.baseCodePath)) {
-                return APK_SIGNING_V2;
-            }
-            return APK_SIGNING_V1;
-        } catch (IOException e) {
+    /** Parses the public keys from the set of signatures. */
+    public static ArraySet<PublicKey> toSigningKeys(Signature[] signatures)
+            throws CertificateException {
+        ArraySet<PublicKey> keys = new ArraySet<>(signatures.length);
+        for (int i = 0; i < signatures.length; i++) {
+            keys.add(signatures[i].getPublicKey());
         }
-        return APK_SIGNING_UNKNOWN;
-    }
-
-    /**
-     * Populates the correct packages fields with the given certificates.
-     * <p>
-     * This is useful when we've already processed the certificates [such as during package
-     * installation through an installer session]. We don't re-process the archive and
-     * simply populate the correct fields.
-     */
-    public static void populateCertificates(Package pkg, Certificate[][] certificates)
-            throws PackageParserException {
-        pkg.mCertificates = null;
-        pkg.mSignatures = null;
-        pkg.mSigningKeys = null;
-
-        pkg.mCertificates = certificates;
-        try {
-            pkg.mSignatures = ApkSignatureVerifier.convertToSignatures(certificates);
-        } catch (CertificateEncodingException e) {
-            // certificates weren't encoded properly; something went wrong
-            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                    "Failed to collect certificates from " + pkg.baseCodePath, e);
-        }
-        pkg.mSigningKeys = new ArraySet<>(certificates.length);
-        for (int i = 0; i < certificates.length; i++) {
-            Certificate[] signerCerts = certificates[i];
-            Certificate signerCert = signerCerts[0];
-            pkg.mSigningKeys.add(signerCert.getPublicKey());
-        }
-        // add signatures to child packages
-        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
-        for (int i = 0; i < childCount; i++) {
-            Package childPkg = pkg.childPackages.get(i);
-            childPkg.mCertificates = pkg.mCertificates;
-            childPkg.mSignatures = pkg.mSignatures;
-            childPkg.mSigningKeys = pkg.mSigningKeys;
-        }
+        return keys;
     }
 
     /**
      * Collect certificates from all the APKs described in the given package,
-     * populating {@link Package#mSignatures}. Also asserts that all APK
+     * populating {@link Package#mSigningDetails}. Also asserts that all APK
      * contents are signed correctly and consistently.
      */
     public static void collectCertificates(Package pkg, @ParseFlags int parseFlags)
@@ -1528,17 +1480,13 @@
         final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
         for (int i = 0; i < childCount; i++) {
             Package childPkg = pkg.childPackages.get(i);
-            childPkg.mCertificates = pkg.mCertificates;
-            childPkg.mSignatures = pkg.mSignatures;
-            childPkg.mSigningKeys = pkg.mSigningKeys;
+            childPkg.mSigningDetails = pkg.mSigningDetails;
         }
     }
 
     private static void collectCertificatesInternal(Package pkg, @ParseFlags int parseFlags)
             throws PackageParserException {
-        pkg.mCertificates = null;
-        pkg.mSignatures = null;
-        pkg.mSigningKeys = null;
+        pkg.mSigningDetails = SigningDetails.UNKNOWN;
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
         try {
@@ -1558,12 +1506,12 @@
             throws PackageParserException {
         final String apkPath = apkFile.getAbsolutePath();
 
-        int minSignatureScheme = ApkSignatureVerifier.VERSION_JAR_SIGNATURE_SCHEME;
+        int minSignatureScheme = SigningDetails.SignatureSchemeVersion.JAR;
         if (pkg.applicationInfo.isStaticSharedLibrary()) {
             // must use v2 signing scheme
-            minSignatureScheme = ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2;
+            minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2;
         }
-        ApkSignatureVerifier.Result verified;
+        SigningDetails verified;
         if ((parseFlags & PARSE_IS_SYSTEM_DIR) != 0) {
             // systemDir APKs are already trusted, save time by not verifying
             verified = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts(
@@ -1571,29 +1519,14 @@
         } else {
             verified = ApkSignatureVerifier.verify(apkPath, minSignatureScheme);
         }
-        if (verified.signatureSchemeVersion
-                < ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2) {
-            // TODO (b/68860689): move this logic to packagemanagerserivce
-            if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) {
-                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                    "No APK Signature Scheme v2 signature in ephemeral package " + apkPath);
-            }
-        }
 
         // Verify that entries are signed consistently with the first pkg
         // we encountered. Note that for splits, certificates may have
         // already been populated during an earlier parse of a base APK.
-        if (pkg.mCertificates == null) {
-            pkg.mCertificates = verified.certs;
-            pkg.mSignatures = verified.sigs;
-            pkg.mSigningKeys = new ArraySet<>(verified.certs.length);
-            for (int i = 0; i < verified.certs.length; i++) {
-                Certificate[] signerCerts = verified.certs[i];
-                Certificate signerCert = signerCerts[0];
-                pkg.mSigningKeys.add(signerCert.getPublicKey());
-            }
+        if (pkg.mSigningDetails == SigningDetails.UNKNOWN) {
+            pkg.mSigningDetails = verified;
         } else {
-            if (!Signature.areExactMatch(pkg.mSignatures, verified.sigs)) {
+            if (!Signature.areExactMatch(pkg.mSigningDetails.signatures, verified.signatures)) {
                 throw new PackageParserException(
                         INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
                         apkPath + " has mismatched certificates");
@@ -1655,8 +1588,7 @@
 
             parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
 
-            final Signature[] signatures;
-            final Certificate[][] certificates;
+            final SigningDetails signingDetails;
             if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
                 // TODO: factor signature related items out of Package object
                 final Package tempPkg = new Package((String) null);
@@ -1666,15 +1598,13 @@
                 } finally {
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
-                signatures = tempPkg.mSignatures;
-                certificates = tempPkg.mCertificates;
+                signingDetails = tempPkg.mSigningDetails;
             } else {
-                signatures = null;
-                certificates = null;
+                signingDetails = SigningDetails.UNKNOWN;
             }
 
             final AttributeSet attrs = parser;
-            return parseApkLite(apkPath, parser, attrs, signatures, certificates);
+            return parseApkLite(apkPath, parser, attrs, signingDetails);
 
         } catch (XmlPullParserException | IOException | RuntimeException e) {
             Slog.w(TAG, "Failed to parse " + apkPath, e);
@@ -1761,7 +1691,7 @@
     }
 
     private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs,
-            Signature[] signatures, Certificate[][] certificates)
+            SigningDetails signingDetails)
             throws IOException, XmlPullParserException, PackageParserException {
         final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);
 
@@ -1854,7 +1784,7 @@
 
         return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
                 configForSplit, usesSplitName, versionCode, versionCodeMajor, revisionCode,
-                installLocation, verifiers, signatures, certificates, coreApp, debuggable,
+                installLocation, verifiers, signingDetails, coreApp, debuggable,
                 multiArch, use32bitAbi, extractNativeLibs, isolatedSplits);
     }
 
@@ -2039,11 +1969,6 @@
         String str = sa.getNonConfigurationString(
                 com.android.internal.R.styleable.AndroidManifest_sharedUserId, 0);
         if (str != null && str.length() > 0) {
-            if ((flags & PARSE_IS_EPHEMERAL) != 0) {
-                outError[0] = "sharedUserId not allowed in ephemeral application";
-                mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
-                return null;
-            }
             String nameError = validateName(str, true, false);
             if (nameError != null && !"android".equals(pkg.packageName)) {
                 outError[0] = "<manifest> specifies bad sharedUserId name \""
@@ -2304,8 +2229,9 @@
                         return null;
                     }
 
+                    boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0;
                     final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers,
-                            targetCode, SDK_VERSION, SDK_CODENAMES, outError);
+                            targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch);
                     if (targetSdkVersion < 0) {
                         mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
                         return null;
@@ -2621,19 +2547,19 @@
      *                   application manifest, or 0 otherwise
      * @param targetCode targetSdkVersion code, if specified in the application
      *                   manifest, or {@code null} otherwise
-     * @param platformSdkVersion platform SDK version number, typically
-     *                           Build.VERSION.SDK_INT
      * @param platformSdkCodenames array of allowed pre-release SDK codenames
      *                             for this platform
      * @param outError output array to populate with error, if applicable
+     * @param forceCurrentDev if development target code is not available, use the current
+     *                        development version by default.
      * @return the targetSdkVersion to use at runtime, or -1 if the package is
      *         not compatible with this platform
      * @hide Exposed for unit testing only.
      */
     @TestApi
     public static int computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
-            @Nullable String targetCode, @IntRange(from = 1) int platformSdkVersion,
-            @NonNull String[] platformSdkCodenames, @NonNull String[] outError) {
+            @Nullable String targetCode, @NonNull String[] platformSdkCodenames,
+            @NonNull String[] outError, boolean forceCurrentDev) {
         // If it's a release SDK, return the version number unmodified.
         if (targetCode == null) {
             return targetVers;
@@ -2641,7 +2567,7 @@
 
         // If it's a pre-release SDK and the codename matches this platform, it
         // definitely targets this SDK.
-        if (ArrayUtils.contains(platformSdkCodenames, targetCode)) {
+        if (ArrayUtils.contains(platformSdkCodenames, targetCode) || forceCurrentDev) {
             return Build.VERSION_CODES.CUR_DEVELOPMENT;
         }
 
@@ -5734,6 +5660,117 @@
         return true;
     }
 
+    /** A container for signing-related data of an application package. */
+    public static final class SigningDetails implements Parcelable {
+
+        @IntDef({SigningDetails.SignatureSchemeVersion.UNKNOWN,
+                SigningDetails.SignatureSchemeVersion.JAR,
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3})
+        public @interface SignatureSchemeVersion {
+            int UNKNOWN = 0;
+            int JAR = 1;
+            int SIGNING_BLOCK_V2 = 2;
+            int SIGNING_BLOCK_V3 = 3;
+        }
+
+        @Nullable
+        public final Signature[] signatures;
+        @SignatureSchemeVersion
+        public final int signatureSchemeVersion;
+        @Nullable
+        public final ArraySet<PublicKey> publicKeys;
+
+        /** A representation of unknown signing details. Use instead of null. */
+        public static final SigningDetails UNKNOWN =
+                new SigningDetails(null, SignatureSchemeVersion.UNKNOWN, null);
+
+        @VisibleForTesting
+        public SigningDetails(Signature[] signatures,
+                @SignatureSchemeVersion int signatureSchemeVersion,
+                ArraySet<PublicKey> keys) {
+            this.signatures = signatures;
+            this.signatureSchemeVersion = signatureSchemeVersion;
+            this.publicKeys = keys;
+        }
+
+        public SigningDetails(Signature[] signatures,
+                @SignatureSchemeVersion int signatureSchemeVersion)
+                throws CertificateException {
+            this(signatures, signatureSchemeVersion, toSigningKeys(signatures));
+        }
+
+        /** Returns true if the signing details have one or more signatures. */
+        public boolean hasSignatures() {
+            return signatures != null && signatures.length > 0;
+        }
+
+        /** Returns true if the signatures in this and other match exactly. */
+        public boolean signaturesMatchExactly(SigningDetails other) {
+            return Signature.areExactMatch(this.signatures, other.signatures);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            boolean isUnknown = UNKNOWN == this;
+            dest.writeBoolean(isUnknown);
+            if (isUnknown) {
+                return;
+            }
+            dest.writeTypedArray(this.signatures, flags);
+            dest.writeInt(this.signatureSchemeVersion);
+            dest.writeArraySet(this.publicKeys);
+        }
+
+        protected SigningDetails(Parcel in) {
+            final ClassLoader boot = Object.class.getClassLoader();
+            this.signatures = in.createTypedArray(Signature.CREATOR);
+            this.signatureSchemeVersion = in.readInt();
+            this.publicKeys = (ArraySet<PublicKey>) in.readArraySet(boot);
+        }
+
+        public static final Creator<SigningDetails> CREATOR = new Creator<SigningDetails>() {
+            @Override
+            public SigningDetails createFromParcel(Parcel source) {
+                if (source.readBoolean()) {
+                    return UNKNOWN;
+                }
+                return new SigningDetails(source);
+            }
+
+            @Override
+            public SigningDetails[] newArray(int size) {
+                return new SigningDetails[size];
+            }
+        };
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof SigningDetails)) return false;
+
+            SigningDetails that = (SigningDetails) o;
+
+            if (signatureSchemeVersion != that.signatureSchemeVersion) return false;
+            if (!Signature.areExactMatch(signatures, that.signatures)) return false;
+            return publicKeys != null ? publicKeys.equals(that.publicKeys)
+                    : that.publicKeys == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = +Arrays.hashCode(signatures);
+            result = 31 * result + signatureSchemeVersion;
+            result = 31 * result + (publicKeys != null ? publicKeys.hashCode() : 0);
+            return result;
+        }
+    }
+
     /**
      * Representation of a full package parsed from APK files on disk. A package
      * consists of a single base APK, and zero or more split APKs.
@@ -5840,8 +5877,7 @@
         public int mSharedUserLabel;
 
         // Signatures that were read from the package.
-        public Signature[] mSignatures;
-        public Certificate[][] mCertificates;
+        @NonNull public SigningDetails mSigningDetails = SigningDetails.UNKNOWN;
 
         // For use by package manager service for quick lookup of
         // preferred up order.
@@ -5893,7 +5929,6 @@
         /**
          * Data used to feed the KeySetManagerService
          */
-        public ArraySet<PublicKey> mSigningKeys;
         public ArraySet<String> mUpgradeKeySets;
         public ArrayMap<String, ArraySet<PublicKey>> mKeySetMapping;
 
@@ -5950,6 +5985,8 @@
             }
         }
 
+        /** @deprecated Forward locked apps no longer supported. Resource path not needed. */
+        @Deprecated
         public void setApplicationInfoResourcePath(String resourcePath) {
             this.applicationInfo.setResourcePath(resourcePath);
             if (childPackages != null) {
@@ -5960,6 +5997,8 @@
             }
         }
 
+        /** @deprecated Forward locked apps no longer supported. Resource path not needed. */
+        @Deprecated
         public void setApplicationInfoBaseResourcePath(String resourcePath) {
             this.applicationInfo.setBaseResourcePath(resourcePath);
             if (childPackages != null) {
@@ -6008,6 +6047,8 @@
             // Children have no splits
         }
 
+        /** @deprecated Forward locked apps no longer supported. Resource path not needed. */
+        @Deprecated
         public void setApplicationInfoSplitResourcePaths(String[] resroucePaths) {
             this.applicationInfo.setSplitResourcePaths(resroucePaths);
             // Children have no splits
@@ -6037,12 +6078,13 @@
             }
         }
 
-        public void setSignatures(Signature[] signatures) {
-            this.mSignatures = signatures;
+        /** Sets signing details on the package and any of its children. */
+        public void setSigningDetails(@NonNull SigningDetails signingDetails) {
+            mSigningDetails = signingDetails;
             if (childPackages != null) {
                 final int packageCount = childPackages.size();
                 for (int i = 0; i < packageCount; i++) {
-                    childPackages.get(i).mSignatures = signatures;
+                    childPackages.get(i).mSigningDetails = signingDetails;
                 }
             }
         }
@@ -6348,8 +6390,7 @@
             }
             mSharedUserLabel = dest.readInt();
 
-            mSignatures = (Signature[]) dest.readParcelableArray(boot, Signature.class);
-            mCertificates = (Certificate[][]) dest.readSerializable();
+            mSigningDetails = dest.readParcelable(boot);
 
             mPreferredOrder = dest.readInt();
 
@@ -6389,7 +6430,6 @@
             mTrustedOverlay = (dest.readInt() == 1);
             mCompileSdkVersion = dest.readInt();
             mCompileSdkVersionCodename = dest.readString();
-            mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot);
             mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot);
 
             mKeySetMapping = readKeySetMapping(dest);
@@ -6489,8 +6529,7 @@
             dest.writeString(mSharedUserId);
             dest.writeInt(mSharedUserLabel);
 
-            dest.writeParcelableArray(mSignatures, flags);
-            dest.writeSerializable(mCertificates);
+            dest.writeParcelable(mSigningDetails, flags);
 
             dest.writeInt(mPreferredOrder);
 
@@ -6515,7 +6554,6 @@
             dest.writeInt(mTrustedOverlay ? 1 : 0);
             dest.writeInt(mCompileSdkVersion);
             dest.writeString(mCompileSdkVersionCodename);
-            dest.writeArraySet(mSigningKeys);
             dest.writeArraySet(mUpgradeKeySets);
             writeKeySetMapping(dest, mKeySetMapping);
             dest.writeString(cpuAbiOverride);
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 069b2d4..293beb2 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -52,6 +52,7 @@
     public int appLinkGeneration;
     public int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
     public int installReason;
+    public String harmfulAppWarning;
 
     public ArraySet<String> disabledComponents;
     public ArraySet<String> enabledComponents;
@@ -87,6 +88,7 @@
         enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
         overlayPaths =
             o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length);
+        harmfulAppWarning = o.harmfulAppWarning;
     }
 
     /**
@@ -247,6 +249,11 @@
                 }
             }
         }
+        if (harmfulAppWarning == null && oldState.harmfulAppWarning != null
+                || (harmfulAppWarning != null
+                        && !harmfulAppWarning.equals(oldState.harmfulAppWarning))) {
+            return false;
+        }
         return true;
     }
 }
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 3239212..97cb78b 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -815,7 +815,7 @@
             } finally {
                 stack.pop();
             }
-        } catch (Exception e) {
+        } catch (Exception | StackOverflowError e) {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
             final NotFoundException rnf = new NotFoundException(
                     "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
diff --git a/core/java/android/hardware/display/BrightnessChangeEvent.java b/core/java/android/hardware/display/BrightnessChangeEvent.java
index 0a08353..2301824 100644
--- a/core/java/android/hardware/display/BrightnessChangeEvent.java
+++ b/core/java/android/hardware/display/BrightnessChangeEvent.java
@@ -16,6 +16,8 @@
 
 package android.hardware.display;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -23,51 +25,65 @@
  * Data about a brightness settings change.
  *
  * {@see DisplayManager.getBrightnessEvents()}
- * TODO make this SystemAPI
  * @hide
  */
+@SystemApi
+@TestApi
 public final class BrightnessChangeEvent implements Parcelable {
     /** Brightness in nits */
-    public float brightness;
+    public final float brightness;
 
     /** Timestamp of the change {@see System.currentTimeMillis()} */
-    public long timeStamp;
+    public final long timeStamp;
 
     /** Package name of focused activity when brightness was changed.
      *  This will be null if the caller of {@see DisplayManager.getBrightnessEvents()}
      *  does not have access to usage stats {@see UsageStatsManager} */
-    public String packageName;
+    public final String packageName;
 
     /** User id of of the user running when brightness was changed.
      * @hide */
-    public int userId;
+    public final int userId;
 
     /** Lux values of recent sensor data */
-    public float[] luxValues;
+    public final float[] luxValues;
 
     /** Timestamps of the lux sensor readings {@see System.currentTimeMillis()} */
-    public long[] luxTimestamps;
+    public final long[] luxTimestamps;
 
     /** Most recent battery level when brightness was changed or Float.NaN */
-    public float batteryLevel;
+    public final float batteryLevel;
 
     /** Color filter active to provide night mode */
-    public boolean nightMode;
+    public final boolean nightMode;
 
     /** If night mode color filter is active this will be the temperature in kelvin */
-    public int colorTemperature;
+    public final int colorTemperature;
 
-    /** Brightness level before slider adjustment */
-    public float lastBrightness;
+    /** Brightness le vel before slider adjustment */
+    public final float lastBrightness;
 
-    public BrightnessChangeEvent() {
+    /** @hide */
+    private BrightnessChangeEvent(float brightness, long timeStamp, String packageName,
+            int userId, float[] luxValues, long[] luxTimestamps, float batteryLevel,
+            boolean nightMode, int colorTemperature, float lastBrightness) {
+        this.brightness = brightness;
+        this.timeStamp = timeStamp;
+        this.packageName = packageName;
+        this.userId = userId;
+        this.luxValues = luxValues;
+        this.luxTimestamps = luxTimestamps;
+        this.batteryLevel = batteryLevel;
+        this.nightMode = nightMode;
+        this.colorTemperature = colorTemperature;
+        this.lastBrightness = lastBrightness;
     }
 
     /** @hide */
-    public BrightnessChangeEvent(BrightnessChangeEvent other) {
+    public BrightnessChangeEvent(BrightnessChangeEvent other, boolean redactPackage) {
         this.brightness = other.brightness;
         this.timeStamp = other.timeStamp;
-        this.packageName = other.packageName;
+        this.packageName = redactPackage ? null : other.packageName;
         this.userId = other.userId;
         this.luxValues = other.luxValues;
         this.luxTimestamps = other.luxTimestamps;
@@ -118,4 +134,85 @@
         dest.writeInt(colorTemperature);
         dest.writeFloat(lastBrightness);
     }
+
+    /** @hide */
+    public static class Builder {
+        private float mBrightness;
+        private long mTimeStamp;
+        private String mPackageName;
+        private int mUserId;
+        private float[] mLuxValues;
+        private long[] mLuxTimestamps;
+        private float mBatteryLevel;
+        private boolean mNightMode;
+        private int mColorTemperature;
+        private float mLastBrightness;
+
+        /** {@see BrightnessChangeEvent#brightness} */
+        public Builder setBrightness(float brightness) {
+            mBrightness = brightness;
+            return this;
+        }
+
+        /** {@see BrightnessChangeEvent#timeStamp} */
+        public Builder setTimeStamp(long timeStamp) {
+            mTimeStamp = timeStamp;
+            return this;
+        }
+
+        /** {@see BrightnessChangeEvent#packageName} */
+        public Builder setPackageName(String packageName) {
+            mPackageName = packageName;
+            return this;
+        }
+
+        /** {@see BrightnessChangeEvent#userId} */
+        public Builder setUserId(int userId) {
+            mUserId = userId;
+            return this;
+        }
+
+        /** {@see BrightnessChangeEvent#luxValues} */
+        public Builder setLuxValues(float[] luxValues) {
+            mLuxValues = luxValues;
+            return this;
+        }
+
+        /** {@see BrightnessChangeEvent#luxTimestamps} */
+        public Builder setLuxTimestamps(long[] luxTimestamps) {
+            mLuxTimestamps = luxTimestamps;
+            return this;
+        }
+
+        /** {@see BrightnessChangeEvent#batteryLevel} */
+        public Builder setBatteryLevel(float batteryLevel) {
+            mBatteryLevel = batteryLevel;
+            return this;
+        }
+
+        /** {@see BrightnessChangeEvent#nightMode} */
+        public Builder setNightMode(boolean nightMode) {
+            mNightMode = nightMode;
+            return this;
+        }
+
+        /** {@see BrightnessChangeEvent#colorTemperature} */
+        public Builder setColorTemperature(int colorTemperature) {
+            mColorTemperature = colorTemperature;
+            return this;
+        }
+
+        /** {@see BrightnessChangeEvent#lastBrightness} */
+        public Builder setLastBrightness(float lastBrightness) {
+            mLastBrightness = lastBrightness;
+            return this;
+        }
+
+        /** Builds a BrightnessChangeEvent */
+        public BrightnessChangeEvent build() {
+            return new BrightnessChangeEvent(mBrightness, mTimeStamp,
+                    mPackageName, mUserId, mLuxValues, mLuxTimestamps, mBatteryLevel,
+                    mNightMode, mColorTemperature, mLastBrightness);
+        }
+    }
 }
diff --git a/core/java/android/hardware/display/BrightnessConfiguration.java b/core/java/android/hardware/display/BrightnessConfiguration.java
index 6c3be81..2156491 100644
--- a/core/java/android/hardware/display/BrightnessConfiguration.java
+++ b/core/java/android/hardware/display/BrightnessConfiguration.java
@@ -16,6 +16,8 @@
 
 package android.hardware.display;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Pair;
@@ -25,6 +27,8 @@
 import java.util.Arrays;
 
 /** @hide */
+@SystemApi
+@TestApi
 public final class BrightnessConfiguration implements Parcelable {
     private final float[] mLux;
     private final float[] mNits;
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 97e9b9c..76ab35d 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -22,6 +22,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.graphics.Point;
@@ -622,6 +623,8 @@
      * Fetch {@link BrightnessChangeEvent}s.
      * @hide until we make it a system api.
      */
+    @SystemApi
+    @TestApi
     @RequiresPermission(Manifest.permission.BRIGHTNESS_SLIDER_USAGE)
     public List<BrightnessChangeEvent> getBrightnessEvents() {
         return mGlobal.getBrightnessEvents(mContext.getOpPackageName());
@@ -632,6 +635,9 @@
      *
      * @hide
      */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
     public void setBrightnessConfiguration(BrightnessConfiguration c) {
         setBrightnessConfigurationForUser(c, UserHandle.myUserId());
     }
diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl
index ca38076..bf5e391 100644
--- a/core/java/android/hardware/radio/ITuner.aidl
+++ b/core/java/android/hardware/radio/ITuner.aidl
@@ -17,6 +17,7 @@
 package android.hardware.radio;
 
 import android.graphics.Bitmap;
+import android.hardware.radio.ProgramList;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 
@@ -73,14 +74,8 @@
      */
     boolean startBackgroundScan();
 
-    /**
-     * @param vendorFilter Vendor-specific filter, must be Map<String, String>
-     * @return the list, or null if scan is in progress
-     * @throws IllegalArgumentException if invalid arguments are passed
-     * @throws IllegalStateException if the scan has not been started, client may
-     *         call startBackgroundScan to fix this.
-     */
-    List<RadioManager.ProgramInfo> getProgramList(in Map vendorFilter);
+    void startProgramListUpdates(in ProgramList.Filter filter);
+    void stopProgramListUpdates();
 
     boolean isConfigFlagSupported(int flag);
     boolean isConfigFlagSet(int flag);
diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl
index 775e25c..54af30f 100644
--- a/core/java/android/hardware/radio/ITunerCallback.aidl
+++ b/core/java/android/hardware/radio/ITunerCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.hardware.radio;
 
+import android.hardware.radio.ProgramList;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioMetadata;
 
@@ -30,6 +31,7 @@
     void onBackgroundScanAvailabilityChange(boolean isAvailable);
     void onBackgroundScanComplete();
     void onProgramListChanged();
+    void onProgramListUpdated(in ProgramList.Chunk chunk);
 
     /**
      * @param parameters Vendor-specific key-value pairs, must be Map<String, String>
diff --git a/telephony/java/android/telephony/data/InterfaceAddress.aidl b/core/java/android/hardware/radio/ProgramList.aidl
similarity index 77%
copy from telephony/java/android/telephony/data/InterfaceAddress.aidl
copy to core/java/android/hardware/radio/ProgramList.aidl
index d750363..34b7f97 100644
--- a/telephony/java/android/telephony/data/InterfaceAddress.aidl
+++ b/core/java/android/hardware/radio/ProgramList.aidl
@@ -1,5 +1,5 @@
-/*
- * Copyright 2017 The Android Open Source Project
+/**
+ * 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.
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-/** @hide */
-package android.telephony.data;
+package android.hardware.radio;
 
-parcelable InterfaceAddress;
+/** @hide */
+parcelable ProgramList.Filter;
+
+/** @hide */
+parcelable ProgramList.Chunk;
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
new file mode 100644
index 0000000..b2aa9ba
--- /dev/null
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -0,0 +1,427 @@
+/**
+ * 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.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class ProgramList implements AutoCloseable {
+
+    private final Object mLock = new Object();
+    private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms =
+            new HashMap<>();
+
+    private final List<ListCallback> mListCallbacks = new ArrayList<>();
+    private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>();
+    private OnCloseListener mOnCloseListener;
+    private boolean mIsClosed = false;
+    private boolean mIsComplete = false;
+
+    ProgramList() {}
+
+    /**
+     * Callback for list change operations.
+     */
+    public abstract static class ListCallback {
+        /**
+         * Called when item was modified or added to the list.
+         */
+        public void onItemChanged(@NonNull ProgramSelector.Identifier id) { }
+
+        /**
+         * Called when item was removed from the list.
+         */
+        public void onItemRemoved(@NonNull ProgramSelector.Identifier id) { }
+    }
+
+    /**
+     * Listener of list complete event.
+     */
+    public interface OnCompleteListener {
+        /**
+         * Called when the list turned complete (i.e. when the scan process
+         * came to an end).
+         */
+        void onComplete();
+    }
+
+    interface OnCloseListener {
+        void onClose();
+    }
+
+    /**
+     * Registers list change callback with executor.
+     */
+    public void registerListCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull ListCallback callback) {
+        registerListCallback(new ListCallback() {
+            public void onItemChanged(@NonNull ProgramSelector.Identifier id) {
+                executor.execute(() -> callback.onItemChanged(id));
+            }
+
+            public void onItemRemoved(@NonNull ProgramSelector.Identifier id) {
+                executor.execute(() -> callback.onItemRemoved(id));
+            }
+        });
+    }
+
+    /**
+     * Registers list change callback.
+     */
+    public void registerListCallback(@NonNull ListCallback callback) {
+        synchronized (mLock) {
+            if (mIsClosed) return;
+            mListCallbacks.add(Objects.requireNonNull(callback));
+        }
+    }
+
+    /**
+     * Unregisters list change callback.
+     */
+    public void unregisterListCallback(@NonNull ListCallback callback) {
+        synchronized (mLock) {
+            if (mIsClosed) return;
+            mListCallbacks.remove(Objects.requireNonNull(callback));
+        }
+    }
+
+    /**
+     * Adds list complete event listener with executor.
+     */
+    public void addOnCompleteListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull OnCompleteListener listener) {
+        addOnCompleteListener(() -> executor.execute(listener::onComplete));
+    }
+
+    /**
+     * Adds list complete event listener.
+     */
+    public void addOnCompleteListener(@NonNull OnCompleteListener listener) {
+        synchronized (mLock) {
+            if (mIsClosed) return;
+            mOnCompleteListeners.add(Objects.requireNonNull(listener));
+            if (mIsComplete) listener.onComplete();
+        }
+    }
+
+    /**
+     * Removes list complete event listener.
+     */
+    public void removeOnCompleteListener(@NonNull OnCompleteListener listener) {
+        synchronized (mLock) {
+            if (mIsClosed) return;
+            mOnCompleteListeners.remove(Objects.requireNonNull(listener));
+        }
+    }
+
+    void setOnCloseListener(@Nullable OnCloseListener listener) {
+        synchronized (mLock) {
+            if (mOnCloseListener != null) {
+                throw new IllegalStateException("Close callback is already set");
+            }
+            mOnCloseListener = listener;
+        }
+    }
+
+    /**
+     * Disables list updates and releases all resources.
+     */
+    public void close() {
+        synchronized (mLock) {
+            if (mIsClosed) return;
+            mIsClosed = true;
+            mPrograms.clear();
+            mListCallbacks.clear();
+            mOnCompleteListeners.clear();
+            if (mOnCloseListener != null) {
+                mOnCloseListener.onClose();
+                mOnCloseListener = null;
+            }
+        }
+    }
+
+    void apply(@NonNull Chunk chunk) {
+        synchronized (mLock) {
+            if (mIsClosed) return;
+
+            mIsComplete = false;
+
+            if (chunk.isPurge()) {
+                new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id));
+            }
+
+            chunk.getRemoved().stream().forEach(id -> removeLocked(id));
+            chunk.getModified().stream().forEach(info -> putLocked(info));
+
+            if (chunk.isComplete()) {
+                mIsComplete = true;
+                mOnCompleteListeners.forEach(cb -> cb.onComplete());
+            }
+        }
+    }
+
+    private void putLocked(@NonNull RadioManager.ProgramInfo value) {
+        ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
+        mPrograms.put(Objects.requireNonNull(key), value);
+        ProgramSelector.Identifier sel = value.getSelector().getPrimaryId();
+        mListCallbacks.forEach(cb -> cb.onItemChanged(sel));
+    }
+
+    private void removeLocked(@NonNull ProgramSelector.Identifier key) {
+        RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
+        if (removed == null) return;
+        ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId();
+        mListCallbacks.forEach(cb -> cb.onItemRemoved(sel));
+    }
+
+    /**
+     * Converts the program list in its current shape to the static List<>.
+     *
+     * @return the new List<> object; it won't receive any further updates
+     */
+    public @NonNull List<RadioManager.ProgramInfo> toList() {
+        synchronized (mLock) {
+            return mPrograms.values().stream().collect(Collectors.toList());
+        }
+    }
+
+    /**
+     * Returns the program with a specified primary identifier.
+     *
+     * @param id primary identifier of a program to fetch
+     * @return the program info, or null if there is no such program on the list
+     */
+    public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
+        synchronized (mLock) {
+            return mPrograms.get(Objects.requireNonNull(id));
+        }
+    }
+
+    /**
+     * Filter for the program list.
+     */
+    public static final class Filter implements Parcelable {
+        private final @NonNull Set<Integer> mIdentifierTypes;
+        private final @NonNull Set<ProgramSelector.Identifier> mIdentifiers;
+        private final boolean mIncludeCategories;
+        private final boolean mExcludeModifications;
+        private final @Nullable Map<String, String> mVendorFilter;
+
+        /**
+         * Constructor of program list filter.
+         *
+         * Arrays passed to this constructor become owned by this object, do not modify them later.
+         *
+         * @param identifierTypes see getIdentifierTypes()
+         * @param identifiers see getIdentifiers()
+         * @param includeCategories see areCategoriesIncluded()
+         * @param excludeModifications see areModificationsExcluded()
+         */
+        public Filter(@NonNull Set<Integer> identifierTypes,
+                @NonNull Set<ProgramSelector.Identifier> identifiers,
+                boolean includeCategories, boolean excludeModifications) {
+            mIdentifierTypes = Objects.requireNonNull(identifierTypes);
+            mIdentifiers = Objects.requireNonNull(identifiers);
+            mIncludeCategories = includeCategories;
+            mExcludeModifications = excludeModifications;
+            mVendorFilter = null;
+        }
+
+        /**
+         * @hide for framework use only
+         */
+        public Filter(@Nullable Map<String, String> vendorFilter) {
+            mIdentifierTypes = Collections.emptySet();
+            mIdentifiers = Collections.emptySet();
+            mIncludeCategories = false;
+            mExcludeModifications = false;
+            mVendorFilter = vendorFilter;
+        }
+
+        private Filter(@NonNull Parcel in) {
+            mIdentifierTypes = Utils.createIntSet(in);
+            mIdentifiers = Utils.createSet(in, ProgramSelector.Identifier.CREATOR);
+            mIncludeCategories = in.readByte() != 0;
+            mExcludeModifications = in.readByte() != 0;
+            mVendorFilter = Utils.readStringMap(in);
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            Utils.writeIntSet(dest, mIdentifierTypes);
+            Utils.writeSet(dest, mIdentifiers);
+            dest.writeByte((byte) (mIncludeCategories ? 1 : 0));
+            dest.writeByte((byte) (mExcludeModifications ? 1 : 0));
+            Utils.writeStringMap(dest, mVendorFilter);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final Parcelable.Creator<Filter> CREATOR = new Parcelable.Creator<Filter>() {
+            public Filter createFromParcel(Parcel in) {
+                return new Filter(in);
+            }
+
+            public Filter[] newArray(int size) {
+                return new Filter[size];
+            }
+        };
+
+        /**
+         * @hide for framework use only
+         */
+        public Map<String, String> getVendorFilter() {
+            return mVendorFilter;
+        }
+
+        /**
+         * Returns the list of identifier types that satisfy the filter.
+         *
+         * If the program list entry contains at least one identifier of the type
+         * listed, it satisfies this condition.
+         *
+         * Empty list means no filtering on identifier type.
+         *
+         * @return the list of accepted identifier types, must not be modified
+         */
+        public @NonNull Set<Integer> getIdentifierTypes() {
+            return mIdentifierTypes;
+        }
+
+        /**
+         * Returns the list of identifiers that satisfy the filter.
+         *
+         * If the program list entry contains at least one listed identifier,
+         * it satisfies this condition.
+         *
+         * Empty list means no filtering on identifier.
+         *
+         * @return the list of accepted identifiers, must not be modified
+         */
+        public @NonNull Set<ProgramSelector.Identifier> getIdentifiers() {
+            return mIdentifiers;
+        }
+
+        /**
+         * Checks, if non-tunable entries that define tree structure on the
+         * program list (i.e. DAB ensembles) should be included.
+         */
+        public boolean areCategoriesIncluded() {
+            return mIncludeCategories;
+        }
+
+        /**
+         * Checks, if updates on entry modifications should be disabled.
+         *
+         * If true, 'modified' vector of ProgramListChunk must contain list
+         * additions only. Once the program is added to the list, it's not
+         * updated anymore.
+         */
+        public boolean areModificationsExcluded() {
+            return mExcludeModifications;
+        }
+    }
+
+    /**
+     * @hide This is a transport class used for internal communication between
+     *       Broadcast Radio Service and RadioManager.
+     *       Do not use it directly.
+     */
+    public static final class Chunk implements Parcelable {
+        private final boolean mPurge;
+        private final boolean mComplete;
+        private final @NonNull Set<RadioManager.ProgramInfo> mModified;
+        private final @NonNull Set<ProgramSelector.Identifier> mRemoved;
+
+        public Chunk(boolean purge, boolean complete,
+                @Nullable Set<RadioManager.ProgramInfo> modified,
+                @Nullable Set<ProgramSelector.Identifier> removed) {
+            mPurge = purge;
+            mComplete = complete;
+            mModified = (modified != null) ? modified : Collections.emptySet();
+            mRemoved = (removed != null) ? removed : Collections.emptySet();
+        }
+
+        private Chunk(@NonNull Parcel in) {
+            mPurge = in.readByte() != 0;
+            mComplete = in.readByte() != 0;
+            mModified = Utils.createSet(in, RadioManager.ProgramInfo.CREATOR);
+            mRemoved = Utils.createSet(in, ProgramSelector.Identifier.CREATOR);
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeByte((byte) (mPurge ? 1 : 0));
+            dest.writeByte((byte) (mComplete ? 1 : 0));
+            Utils.writeSet(dest, mModified);
+            Utils.writeSet(dest, mRemoved);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final Parcelable.Creator<Chunk> CREATOR = new Parcelable.Creator<Chunk>() {
+            public Chunk createFromParcel(Parcel in) {
+                return new Chunk(in);
+            }
+
+            public Chunk[] newArray(int size) {
+                return new Chunk[size];
+            }
+        };
+
+        public boolean isPurge() {
+            return mPurge;
+        }
+
+        public boolean isComplete() {
+            return mComplete;
+        }
+
+        public @NonNull Set<RadioManager.ProgramInfo> getModified() {
+            return mModified;
+        }
+
+        public @NonNull Set<ProgramSelector.Identifier> getRemoved() {
+            return mRemoved;
+        }
+    }
+}
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 2211cee..3556751 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -59,6 +59,7 @@
  */
 @SystemApi
 public final class ProgramSelector implements Parcelable {
+    public static final int PROGRAM_TYPE_INVALID = 0;
     /** Analogue AM radio (with or without RDS). */
     public static final int PROGRAM_TYPE_AM = 1;
     /** analogue FM radio (with or without RDS). */
@@ -77,6 +78,7 @@
     public static final int PROGRAM_TYPE_VENDOR_START = 1000;
     public static final int PROGRAM_TYPE_VENDOR_END = 1999;
     @IntDef(prefix = { "PROGRAM_TYPE_" }, value = {
+        PROGRAM_TYPE_INVALID,
         PROGRAM_TYPE_AM,
         PROGRAM_TYPE_FM,
         PROGRAM_TYPE_AM_HD,
@@ -89,6 +91,7 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProgramType {}
 
+    public static final int IDENTIFIER_TYPE_INVALID = 0;
     /** kHz */
     public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1;
     /** 16bit */
@@ -148,6 +151,7 @@
     public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = PROGRAM_TYPE_VENDOR_START;
     public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = PROGRAM_TYPE_VENDOR_END;
     @IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = {
+        IDENTIFIER_TYPE_INVALID,
         IDENTIFIER_TYPE_AMFM_FREQUENCY,
         IDENTIFIER_TYPE_RDS_PI,
         IDENTIFIER_TYPE_HD_STATION_ID_EXT,
@@ -268,7 +272,7 @@
      * Vendor identifiers are passed as-is to the HAL implementation,
      * preserving elements order.
      *
-     * @return a array of vendor identifiers, must not be modified.
+     * @return an array of vendor identifiers, must not be modified.
      */
     public @NonNull long[] getVendorIds() {
         return mVendorIds;
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index b740f14..56668ac 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -185,25 +185,6 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ConfigFlag {}
 
-    private static void writeStringMap(@NonNull Parcel dest, @NonNull Map<String, String> map) {
-        dest.writeInt(map.size());
-        for (Map.Entry<String, String> entry : map.entrySet()) {
-            dest.writeString(entry.getKey());
-            dest.writeString(entry.getValue());
-        }
-    }
-
-    private static @NonNull Map<String, String> readStringMap(@NonNull Parcel in) {
-        int size = in.readInt();
-        Map<String, String> map = new HashMap<>();
-        while (size-- > 0) {
-            String key = in.readString();
-            String value = in.readString();
-            map.put(key, value);
-        }
-        return map;
-    }
-
     /*****************************************************************************
      * Lists properties, options and radio bands supported by a given broadcast radio module.
      * Each module has a unique ID used to address it when calling RadioManager APIs.
@@ -415,7 +396,7 @@
             mIsBgScanSupported = in.readInt() == 1;
             mSupportedProgramTypes = arrayToSet(in.createIntArray());
             mSupportedIdentifierTypes = arrayToSet(in.createIntArray());
-            mVendorInfo = readStringMap(in);
+            mVendorInfo = Utils.readStringMap(in);
         }
 
         public static final Parcelable.Creator<ModuleProperties> CREATOR
@@ -445,7 +426,7 @@
             dest.writeInt(mIsBgScanSupported ? 1 : 0);
             dest.writeIntArray(setToArray(mSupportedProgramTypes));
             dest.writeIntArray(setToArray(mSupportedIdentifierTypes));
-            writeStringMap(dest, mVendorInfo);
+            Utils.writeStringMap(dest, mVendorInfo);
         }
 
         @Override
@@ -1410,7 +1391,7 @@
         private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3;
 
         @NonNull private final ProgramSelector mSelector;
-        private final boolean mTuned;
+        private final boolean mTuned;  // TODO(b/69958777): replace with mFlags
         private final boolean mStereo;
         private final boolean mDigital;
         private final int mFlags;
@@ -1418,7 +1399,8 @@
         private final RadioMetadata mMetadata;
         @NonNull private final Map<String, String> mVendorInfo;
 
-        ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo,
+        /** @hide */
+        public ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo,
                 boolean digital, int signalStrength, RadioMetadata metadata, int flags,
                 Map<String, String> vendorInfo) {
             mSelector = selector;
@@ -1564,7 +1546,7 @@
                 mMetadata = null;
             }
             mFlags = in.readInt();
-            mVendorInfo = readStringMap(in);
+            mVendorInfo = Utils.readStringMap(in);
         }
 
         public static final Parcelable.Creator<ProgramInfo> CREATOR
@@ -1592,7 +1574,7 @@
                 mMetadata.writeToParcel(dest, flags);
             }
             dest.writeInt(mFlags);
-            writeStringMap(dest, mVendorInfo);
+            Utils.writeStringMap(dest, mVendorInfo);
         }
 
         @Override
@@ -1727,7 +1709,8 @@
             Log.e(TAG, "Failed to open tuner");
             return null;
         }
-        return new TunerAdapter(tuner, config != null ? config.getType() : BAND_INVALID);
+        return new TunerAdapter(tuner, halCallback,
+                config != null ? config.getType() : BAND_INVALID);
     }
 
     @NonNull private final Context mContext;
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
index 0d367e7..ed20c4a 100644
--- a/core/java/android/hardware/radio/RadioTuner.java
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -280,11 +280,29 @@
      * @throws IllegalStateException if the scan is in progress or has not been started,
      *         startBackgroundScan() call may fix it.
      * @throws IllegalArgumentException if the vendorFilter argument is not valid.
+     * @deprecated Use {@link getDynamicProgramList} instead.
      */
+    @Deprecated
     public abstract @NonNull List<RadioManager.ProgramInfo>
             getProgramList(@Nullable Map<String, String> vendorFilter);
 
     /**
+     * Get the dynamic list of discovered radio stations.
+     *
+     * The list object is updated asynchronously; to get the updates register
+     * with {@link ProgramList#addListCallback}.
+     *
+     * When the returned object is no longer used, it must be closed.
+     *
+     * @param filter filter for the list, or null to get the full list.
+     * @return the dynamic program list object, close it after use
+     *         or {@code null} if program list is not supported by the tuner
+     */
+    public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
+        return null;
+    }
+
+    /**
      * Checks, if the analog playback is forced, see setAnalogForced.
      *
      * @throws IllegalStateException if the switch is not supported at current
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index 8ad609d..91944bf 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -33,15 +33,18 @@
     private static final String TAG = "BroadcastRadio.TunerAdapter";
 
     @NonNull private final ITuner mTuner;
+    @NonNull private final TunerCallbackAdapter mCallback;
     private boolean mIsClosed = false;
 
     private @RadioManager.Band int mBand;
 
-    TunerAdapter(ITuner tuner, @RadioManager.Band int band) {
-        if (tuner == null) {
-            throw new NullPointerException();
-        }
-        mTuner = tuner;
+    private ProgramList mLegacyListProxy;
+    private Map<String, String> mLegacyListFilter;
+
+    TunerAdapter(@NonNull ITuner tuner, @NonNull TunerCallbackAdapter callback,
+            @RadioManager.Band int band) {
+        mTuner = Objects.requireNonNull(tuner);
+        mCallback = Objects.requireNonNull(callback);
         mBand = band;
     }
 
@@ -53,6 +56,10 @@
                 return;
             }
             mIsClosed = true;
+            if (mLegacyListProxy != null) {
+                mLegacyListProxy.close();
+                mLegacyListProxy = null;
+            }
         }
         try {
             mTuner.close();
@@ -227,10 +234,55 @@
     @Override
     public @NonNull List<RadioManager.ProgramInfo>
             getProgramList(@Nullable Map<String, String> vendorFilter) {
-        try {
-            return mTuner.getProgramList(vendorFilter);
-        } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+        synchronized (mTuner) {
+            if (mLegacyListProxy == null || !Objects.equals(mLegacyListFilter, vendorFilter)) {
+                Log.i(TAG, "Program list filter has changed, requesting new list");
+                mLegacyListProxy = new ProgramList();
+                mLegacyListFilter = vendorFilter;
+
+                mCallback.clearLastCompleteList();
+                mCallback.setProgramListObserver(mLegacyListProxy, () -> { });
+                try {
+                    mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter));
+                } catch (RemoteException ex) {
+                    throw new RuntimeException("service died", ex);
+                }
+            }
+
+            List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList();
+            if (list == null) throw new IllegalStateException("Program list is not ready yet");
+            return list;
+        }
+    }
+
+    @Override
+    public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
+        synchronized (mTuner) {
+            if (mLegacyListProxy != null) {
+                mLegacyListProxy.close();
+                mLegacyListProxy = null;
+            }
+            mLegacyListFilter = null;
+
+            ProgramList list = new ProgramList();
+            mCallback.setProgramListObserver(list, () -> {
+                try {
+                    mTuner.stopProgramListUpdates();
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Couldn't stop program list updates", ex);
+                }
+            });
+
+            try {
+                mTuner.startProgramListUpdates(filter);
+            } catch (UnsupportedOperationException ex) {
+                return null;
+            } catch (RemoteException ex) {
+                mCallback.setProgramListObserver(null, () -> { });
+                throw new RuntimeException("service died", ex);
+            }
+
+            return list;
         }
     }
 
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index a01f658..b299ffe 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -22,7 +22,9 @@
 import android.os.Looper;
 import android.util.Log;
 
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback.
@@ -30,9 +32,14 @@
 class TunerCallbackAdapter extends ITunerCallback.Stub {
     private static final String TAG = "BroadcastRadio.TunerCallbackAdapter";
 
+    private final Object mLock = new Object();
     @NonNull private final RadioTuner.Callback mCallback;
     @NonNull private final Handler mHandler;
 
+    @Nullable ProgramList mProgramList;
+    @Nullable List<RadioManager.ProgramInfo> mLastCompleteList;  // for legacy getProgramList call
+    private boolean mDelayedCompleteCallback = false;
+
     TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) {
         mCallback = callback;
         if (handler == null) {
@@ -42,6 +49,49 @@
         }
     }
 
+    void setProgramListObserver(@Nullable ProgramList programList,
+            @NonNull ProgramList.OnCloseListener closeListener) {
+        Objects.requireNonNull(closeListener);
+        synchronized (mLock) {
+            if (mProgramList != null) {
+                Log.w(TAG, "Previous program list observer wasn't properly closed, closing it...");
+                mProgramList.close();
+            }
+            mProgramList = programList;
+            if (programList == null) return;
+            programList.setOnCloseListener(() -> {
+                synchronized (mLock) {
+                    if (mProgramList != programList) return;
+                    mProgramList = null;
+                    mLastCompleteList = null;
+                    closeListener.onClose();
+                }
+            });
+            programList.addOnCompleteListener(() -> {
+                synchronized (mLock) {
+                    if (mProgramList != programList) return;
+                    mLastCompleteList = programList.toList();
+                    if (mDelayedCompleteCallback) {
+                        Log.d(TAG, "Sending delayed onBackgroundScanComplete callback");
+                        sendBackgroundScanCompleteLocked();
+                    }
+                }
+            });
+        }
+    }
+
+    @Nullable List<RadioManager.ProgramInfo> getLastCompleteList() {
+        synchronized (mLock) {
+            return mLastCompleteList;
+        }
+    }
+
+    void clearLastCompleteList() {
+        synchronized (mLock) {
+            mLastCompleteList = null;
+        }
+    }
+
     @Override
     public void onError(int status) {
         mHandler.post(() -> mCallback.onError(status));
@@ -87,9 +137,22 @@
         mHandler.post(() -> mCallback.onBackgroundScanAvailabilityChange(isAvailable));
     }
 
+    private void sendBackgroundScanCompleteLocked() {
+        mDelayedCompleteCallback = false;
+        mHandler.post(() -> mCallback.onBackgroundScanComplete());
+    }
+
     @Override
     public void onBackgroundScanComplete() {
-        mHandler.post(() -> mCallback.onBackgroundScanComplete());
+        synchronized (mLock) {
+            if (mLastCompleteList == null) {
+                Log.i(TAG, "Got onBackgroundScanComplete callback, but the "
+                        + "program list didn't get through yet. Delaying it...");
+                mDelayedCompleteCallback = true;
+                return;
+            }
+            sendBackgroundScanCompleteLocked();
+        }
     }
 
     @Override
@@ -98,6 +161,14 @@
     }
 
     @Override
+    public void onProgramListUpdated(ProgramList.Chunk chunk) {
+        synchronized (mLock) {
+            if (mProgramList == null) return;
+            mProgramList.apply(Objects.requireNonNull(chunk));
+        }
+    }
+
+    @Override
     public void onParametersUpdated(Map parameters) {
         mHandler.post(() -> mCallback.onParametersUpdated(parameters));
     }
diff --git a/core/java/android/hardware/radio/Utils.java b/core/java/android/hardware/radio/Utils.java
new file mode 100644
index 0000000..09bf8fe
--- /dev/null
+++ b/core/java/android/hardware/radio/Utils.java
@@ -0,0 +1,92 @@
+/**
+ * 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.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+final class Utils {
+    static void writeStringMap(@NonNull Parcel dest, @Nullable Map<String, String> map) {
+        if (map == null) {
+            dest.writeInt(0);
+            return;
+        }
+        dest.writeInt(map.size());
+        for (Map.Entry<String, String> entry : map.entrySet()) {
+            dest.writeString(entry.getKey());
+            dest.writeString(entry.getValue());
+        }
+    }
+
+    static @NonNull Map<String, String> readStringMap(@NonNull Parcel in) {
+        int size = in.readInt();
+        Map<String, String> map = new HashMap<>();
+        while (size-- > 0) {
+            String key = in.readString();
+            String value = in.readString();
+            map.put(key, value);
+        }
+        return map;
+    }
+
+    static <T extends Parcelable> void writeSet(@NonNull Parcel dest, @Nullable Set<T> set) {
+        if (set == null) {
+            dest.writeInt(0);
+            return;
+        }
+        dest.writeInt(set.size());
+        set.stream().forEach(elem -> dest.writeTypedObject(elem, 0));
+    }
+
+    static <T> Set<T> createSet(@NonNull Parcel in, Parcelable.Creator<T> c) {
+        int size = in.readInt();
+        Set<T> set = new HashSet<>();
+        while (size-- > 0) {
+            set.add(in.readTypedObject(c));
+        }
+        return set;
+    }
+
+    static void writeIntSet(@NonNull Parcel dest, @Nullable Set<Integer> set) {
+        if (set == null) {
+            dest.writeInt(0);
+            return;
+        }
+        dest.writeInt(set.size());
+        set.stream().forEach(elem -> dest.writeInt(Objects.requireNonNull(elem)));
+    }
+
+    static Set<Integer> createIntSet(@NonNull Parcel in) {
+        return createSet(in, new Parcelable.Creator<Integer>() {
+            public Integer createFromParcel(Parcel in) {
+                return in.readInt();
+            }
+
+            public Integer[] newArray(int size) {
+                return new Integer[size];
+            }
+        });
+    }
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8937490..7528bc3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -340,42 +340,35 @@
     final Insets mTmpInsets = new Insets();
     final int[] mTmpLocation = new int[2];
 
-    final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
-            new ViewTreeObserver.OnComputeInternalInsetsListener() {
-        public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
-            if (isExtractViewShown()) {
-                // In true fullscreen mode, we just say the window isn't covering
-                // any content so we don't impact whatever is behind.
-                View decor = getWindow().getWindow().getDecorView();
-                info.contentInsets.top = info.visibleInsets.top
-                        = decor.getHeight();
-                info.touchableRegion.setEmpty();
-                info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
-            } else {
-                onComputeInsets(mTmpInsets);
-                info.contentInsets.top = mTmpInsets.contentTopInsets;
-                info.visibleInsets.top = mTmpInsets.visibleTopInsets;
-                info.touchableRegion.set(mTmpInsets.touchableRegion);
-                info.setTouchableInsets(mTmpInsets.touchableInsets);
+    final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
+        if (isExtractViewShown()) {
+            // In true fullscreen mode, we just say the window isn't covering
+            // any content so we don't impact whatever is behind.
+            View decor = getWindow().getWindow().getDecorView();
+            info.contentInsets.top = info.visibleInsets.top = decor.getHeight();
+            info.touchableRegion.setEmpty();
+            info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+        } else {
+            onComputeInsets(mTmpInsets);
+            info.contentInsets.top = mTmpInsets.contentTopInsets;
+            info.visibleInsets.top = mTmpInsets.visibleTopInsets;
+            info.touchableRegion.set(mTmpInsets.touchableRegion);
+            info.setTouchableInsets(mTmpInsets.touchableInsets);
+        }
+    };
+
+    final View.OnClickListener mActionClickListener = v -> {
+        final EditorInfo ei = getCurrentInputEditorInfo();
+        final InputConnection ic = getCurrentInputConnection();
+        if (ei != null && ic != null) {
+            if (ei.actionId != 0) {
+                ic.performEditorAction(ei.actionId);
+            } else if ((ei.imeOptions & EditorInfo.IME_MASK_ACTION) != EditorInfo.IME_ACTION_NONE) {
+                ic.performEditorAction(ei.imeOptions & EditorInfo.IME_MASK_ACTION);
             }
         }
     };
 
-    final View.OnClickListener mActionClickListener = new View.OnClickListener() {
-        public void onClick(View v) {
-            final EditorInfo ei = getCurrentInputEditorInfo();
-            final InputConnection ic = getCurrentInputConnection();
-            if (ei != null && ic != null) {
-                if (ei.actionId != 0) {
-                    ic.performEditorAction(ei.actionId);
-                } else if ((ei.imeOptions&EditorInfo.IME_MASK_ACTION)
-                        != EditorInfo.IME_ACTION_NONE) {
-                    ic.performEditorAction(ei.imeOptions&EditorInfo.IME_MASK_ACTION);
-                }
-            }
-        }
-    };
-    
     /**
      * Concrete implementation of
      * {@link AbstractInputMethodService.AbstractInputMethodImpl} that provides
@@ -896,20 +889,20 @@
             mWindow.getWindow().setWindowAnimations(
                     com.android.internal.R.style.Animation_InputMethodFancy);
         }
-        mFullscreenArea = (ViewGroup)mRootView.findViewById(com.android.internal.R.id.fullscreenArea);
+        mFullscreenArea = mRootView.findViewById(com.android.internal.R.id.fullscreenArea);
         mExtractViewHidden = false;
-        mExtractFrame = (FrameLayout)mRootView.findViewById(android.R.id.extractArea);
+        mExtractFrame = mRootView.findViewById(android.R.id.extractArea);
         mExtractView = null;
         mExtractEditText = null;
         mExtractAccessories = null;
         mExtractAction = null;
         mFullscreenApplied = false;
-        
-        mCandidatesFrame = (FrameLayout)mRootView.findViewById(android.R.id.candidatesArea);
-        mInputFrame = (FrameLayout)mRootView.findViewById(android.R.id.inputArea);
+
+        mCandidatesFrame = mRootView.findViewById(android.R.id.candidatesArea);
+        mInputFrame = mRootView.findViewById(android.R.id.inputArea);
         mInputView = null;
         mIsInputViewShown = false;
-        
+
         mExtractFrame.setVisibility(View.GONE);
         mCandidatesVisibility = getCandidatesHiddenVisibility();
         mCandidatesFrame.setVisibility(mCandidatesVisibility);
@@ -1089,33 +1082,6 @@
     }
 
     /**
-     * Close/hide the input method's soft input area, so the user no longer
-     * sees it or can interact with it.  This can only be called
-     * from the currently active input method, as validated by the given token.
-     *
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY},
-     * {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set.
-     */
-    public void hideSoftInputFromInputMethod(int flags) {
-        mImm.hideSoftInputFromInputMethodInternal(mToken, flags);
-    }
-
-    /**
-     * Show the input method's soft input area, so the user
-     * sees the input method window and can interact with it.
-     * This can only be called from the currently active input method,
-     * as validated by the given token.
-     *
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT} or
-     * {@link InputMethodManager#SHOW_FORCED} bit set.
-     */
-    public void showSoftInputFromInputMethod(int flags) {
-        mImm.showSoftInputFromInputMethodInternal(mToken, flags);
-    }
-
-    /**
      * Force switch to the last used input method and subtype. If the last input method didn't have
      * any subtypes, the framework will simply switch to the last input method with no subtype
      * specified.
@@ -1461,17 +1427,17 @@
     public int getCandidatesHiddenVisibility() {
         return isExtractViewShown() ? View.GONE : View.INVISIBLE;
     }
-    
+
     public void showStatusIcon(@DrawableRes int iconResId) {
         mStatusIcon = iconResId;
-        mImm.showStatusIcon(mToken, getPackageName(), iconResId);
+        mImm.showStatusIconInternal(mToken, getPackageName(), iconResId);
     }
-    
+
     public void hideStatusIcon() {
         mStatusIcon = 0;
-        mImm.hideStatusIcon(mToken);
+        mImm.hideStatusIconInternal(mToken);
     }
-    
+
     /**
      * Force switch to a new input method, as identified by <var>id</var>.  This
      * input method will be destroyed, and the requested one started on the
@@ -1480,9 +1446,9 @@
      * @param id Unique identifier of the new input method ot start.
      */
     public void switchInputMethod(String id) {
-        mImm.setInputMethod(mToken, id);
+        mImm.setInputMethodInternal(mToken, id);
     }
-    
+
     public void setExtractView(View view) {
         mExtractFrame.removeAllViews();
         mExtractFrame.addView(view, new FrameLayout.LayoutParams(
@@ -1490,13 +1456,13 @@
                 ViewGroup.LayoutParams.MATCH_PARENT));
         mExtractView = view;
         if (view != null) {
-            mExtractEditText = (ExtractEditText)view.findViewById(
+            mExtractEditText = view.findViewById(
                     com.android.internal.R.id.inputExtractEditText);
             mExtractEditText.setIME(this);
             mExtractAction = view.findViewById(
                     com.android.internal.R.id.inputExtractAction);
             if (mExtractAction != null) {
-                mExtractAccessories = (ViewGroup)view.findViewById(
+                mExtractAccessories = view.findViewById(
                         com.android.internal.R.id.inputExtractAccessories);
             }
             startExtractingText(false);
@@ -1745,7 +1711,7 @@
             // Rethrow the exception to preserve the existing behavior.  Some IMEs may have directly
             // called this method and relied on this exception for some clean-up tasks.
             // TODO: Give developers a clear guideline of whether it's OK to call this method or
-            // InputMethodManager#showSoftInputFromInputMethod() should always be used instead.
+            // InputMethodService#requestShowSelf(int) should always be used instead.
             throw e;
         } finally {
             // TODO: Is it OK to set true when we get BadTokenException?
@@ -2067,27 +2033,30 @@
 
     /**
      * Close this input method's soft input area, removing it from the display.
-     * The input method will continue running, but the user can no longer use
-     * it to generate input by touching the screen.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY
-     * InputMethodManager.HIDE_IMPLICIT_ONLY} bit set.
+     *
+     * The input method will continue running, but the user can no longer use it to generate input
+     * by touching the screen.
+     *
+     * @see InputMethodManager#HIDE_IMPLICIT_ONLY
+     * @see InputMethodManager#HIDE_NOT_ALWAYS
+     * @param flags Provides additional operating flags.
      */
     public void requestHideSelf(int flags) {
-        mImm.hideSoftInputFromInputMethod(mToken, flags);
+        mImm.hideSoftInputFromInputMethodInternal(mToken, flags);
     }
-    
+
     /**
-     * Show the input method. This is a call back to the
-     * IMF to handle showing the input method.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link InputMethodManager#SHOW_FORCED
-     * InputMethodManager.} bit set.
+     * Show the input method's soft input area, so the user sees the input method window and can
+     * interact with it.
+     *
+     * @see InputMethodManager#SHOW_IMPLICIT
+     * @see InputMethodManager#SHOW_FORCED
+     * @param flags Provides additional operating flags.
      */
-    private void requestShowSelf(int flags) {
-        mImm.showSoftInputFromInputMethod(mToken, flags);
+    public void requestShowSelf(int flags) {
+        mImm.showSoftInputFromInputMethodInternal(mToken, flags);
     }
-    
+
     private boolean handleBack(boolean doIt) {
         if (mShowInputRequested) {
             // If the soft input area is shown, back closes it and we
@@ -2754,7 +2723,7 @@
      * application.
      * This cannot be {@code null}.
      * @param inputConnection {@link InputConnection} with which
-     * {@link InputConnection#commitContent(InputContentInfo, Bundle)} will be called.
+     * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} will be called.
      * @hide
      */
     @Override
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index d6992aa..287bdc8 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -60,7 +61,7 @@
     })
     public @interface MacAddressType { }
 
-    /** Indicates a MAC address of unknown type. */
+    /** @hide Indicates a MAC address of unknown type. */
     public static final int TYPE_UNKNOWN = 0;
     /** Indicates a MAC address is a unicast address. */
     public static final int TYPE_UNICAST = 1;
@@ -92,7 +93,7 @@
      *
      * @return the int constant representing the MAC address type of this MacAddress.
      */
-    public @MacAddressType int addressType() {
+    public @MacAddressType int getAddressType() {
         if (equals(BROADCAST_ADDRESS)) {
             return TYPE_BROADCAST;
         }
@@ -120,12 +121,12 @@
     /**
      * @return a byte array representation of this MacAddress.
      */
-    public byte[] toByteArray() {
+    public @NonNull byte[] toByteArray() {
         return byteAddrFromLongAddr(mAddr);
     }
 
     @Override
-    public String toString() {
+    public @NonNull String toString() {
         return stringAddrFromLongAddr(mAddr);
     }
 
@@ -133,7 +134,7 @@
      * @return a String representation of the OUI part of this MacAddress made of 3 hexadecimal
      * numbers in [0,ff] joined by ':' characters.
      */
-    public String toOuiString() {
+    public @NonNull String toOuiString() {
         return String.format(
                 "%02x:%02x:%02x", (mAddr >> 40) & 0xff, (mAddr >> 32) & 0xff, (mAddr >> 24) & 0xff);
     }
@@ -197,7 +198,7 @@
         if (!isMacAddress(addr)) {
             return TYPE_UNKNOWN;
         }
-        return MacAddress.fromBytes(addr).addressType();
+        return MacAddress.fromBytes(addr).getAddressType();
     }
 
     /**
@@ -211,7 +212,7 @@
      *
      * @hide
      */
-    public static byte[] byteAddrFromStringAddr(String addr) {
+    public static @NonNull byte[] byteAddrFromStringAddr(String addr) {
         Preconditions.checkNotNull(addr);
         String[] parts = addr.split(":");
         if (parts.length != ETHER_ADDR_LEN) {
@@ -239,7 +240,7 @@
      *
      * @hide
      */
-    public static String stringAddrFromByteAddr(byte[] addr) {
+    public static @NonNull String stringAddrFromByteAddr(byte[] addr) {
         if (!isMacAddress(addr)) {
             return null;
         }
@@ -291,7 +292,7 @@
 
     // Internal conversion function equivalent to stringAddrFromByteAddr(byteAddrFromLongAddr(addr))
     // that avoids the allocation of an intermediary byte[].
-    private static String stringAddrFromLongAddr(long addr) {
+    private static @NonNull String stringAddrFromLongAddr(long addr) {
         return String.format("%02x:%02x:%02x:%02x:%02x:%02x",
                 (addr >> 40) & 0xff,
                 (addr >> 32) & 0xff,
@@ -310,7 +311,7 @@
      * @return the MacAddress corresponding to the given String representation.
      * @throws IllegalArgumentException if the given String is not a valid representation.
      */
-    public static MacAddress fromString(String addr) {
+    public static @NonNull MacAddress fromString(@NonNull String addr) {
         return new MacAddress(longAddrFromStringAddr(addr));
     }
 
@@ -322,7 +323,7 @@
      * @return the MacAddress corresponding to the given byte array representation.
      * @throws IllegalArgumentException if the given byte array is not a valid representation.
      */
-    public static MacAddress fromBytes(byte[] addr) {
+    public static @NonNull MacAddress fromBytes(@NonNull byte[] addr) {
         return new MacAddress(longAddrFromByteAddr(addr));
     }
 
@@ -336,7 +337,7 @@
      *
      * @hide
      */
-    public static MacAddress createRandomUnicastAddress() {
+    public static @NonNull MacAddress createRandomUnicastAddress() {
         return createRandomUnicastAddress(BASE_GOOGLE_MAC, new Random());
     }
 
@@ -352,7 +353,7 @@
      *
      * @hide
      */
-    public static MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
+    public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
         long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
         addr = addr | LOCALLY_ASSIGNED_MASK;
         addr = addr & ~MULTICAST_MASK;
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 1a3ce91..5df168d 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -357,13 +357,13 @@
         // Multiple Provisioning Domains API recommendations, as made by the
         // IETF mif working group.
         //
-        // The HANDLE_MAGIC value MUST be kept in sync with the corresponding
+        // The handleMagic value MUST be kept in sync with the corresponding
         // value in the native/android/net.c NDK implementation.
         if (netId == 0) {
             return 0L;  // make this zero condition obvious for debugging
         }
-        final long HANDLE_MAGIC = 0xfacade;
-        return (((long) netId) << 32) | HANDLE_MAGIC;
+        final long handleMagic = 0xcafed00dL;
+        return (((long) netId) << 32) | handleMagic;
     }
 
     // implement the Parcelable interface
diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java
index 7277ba3..bb36536 100644
--- a/core/java/android/net/metrics/WakeupStats.java
+++ b/core/java/android/net/metrics/WakeupStats.java
@@ -80,7 +80,7 @@
                 break;
         }
 
-        switch (ev.dstHwAddr.addressType()) {
+        switch (ev.dstHwAddr.getAddressType()) {
             case MacAddress.TYPE_UNICAST:
                 l2UnicastCount++;
                 break;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 77e4808..dc271d8 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -235,8 +235,11 @@
      * New in version 29:
      *   - Process states re-ordered. TOP_SLEEPING now below BACKGROUND. HEAVY_WEIGHT introduced.
      *   - CPU times per UID process state
+     * New in version 30:
+     *   - Uid.PROCESS_STATE_FOREGROUND_SERVICE only tracks
+     *   ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE.
      */
-    static final int CHECKIN_VERSION = 29;
+    static final int CHECKIN_VERSION = 30;
 
     /**
      * Old version, we hit 9 and ran out of room, need to remove.
@@ -694,17 +697,17 @@
         // total time a uid has had any processes running at all.
 
         /**
-         * Time this uid has any processes in the top state (or above such as persistent).
+         * Time this uid has any processes in the top state.
          */
         public static final int PROCESS_STATE_TOP = 0;
         /**
-         * Time this uid has any process with a started out bound foreground service, but
+         * Time this uid has any process with a started foreground service, but
          * none in the "top" state.
          */
         public static final int PROCESS_STATE_FOREGROUND_SERVICE = 1;
         /**
          * Time this uid has any process in an active foreground state, but none in the
-         * "top sleeping" or better state.
+         * "foreground service" or better state. Persistent and other foreground states go here.
          */
         public static final int PROCESS_STATE_FOREGROUND = 2;
         /**
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 01d6b02..65e9473 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -97,7 +97,7 @@
     boolean isUserRunning(int userId);
     boolean isUserNameSet(int userHandle);
     boolean hasRestrictedProfiles();
-    boolean trySetQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userHandle, in IntentSender target);
+    boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userHandle, in IntentSender target);
     long getUserStartRealtime();
     long getUserUnlockRealtime();
 }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index cd6d41b..3798a5e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -565,6 +565,42 @@
         int OPTIONAL_SENSORS = 13;
     }
 
+    /**
+     * Either the location providers shouldn't be affected by battery saver,
+     * or battery saver is off.
+     */
+    public static final int LOCATION_MODE_NO_CHANGE = 0;
+
+    /**
+     * In this mode, the GPS based location provider should be disabled when battery saver is on and
+     * the device is non-interactive.
+     */
+    public static final int LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF = 1;
+
+    /**
+     * All location providers should be disabled when battery saver is on and
+     * the device is non-interactive.
+     */
+    public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2;
+
+    /**
+     * In this mode, all the location providers will be kept available, but location fixes
+     * should only be provided to foreground apps.
+     */
+    public static final int LOCATION_MODE_FOREGROUND_ONLY = 3;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"LOCATION_MODE_"}, value = {
+            LOCATION_MODE_NO_CHANGE,
+            LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF,
+            LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF,
+            LOCATION_MODE_FOREGROUND_ONLY,
+    })
+    public @interface LocationPowerSaveMode {}
+
     final Context mContext;
     final IPowerManager mService;
     final Handler mHandler;
@@ -1143,6 +1179,24 @@
     }
 
     /**
+     * Returns how location features should behave when battery saver is on. When battery saver
+     * is off, this will always return {@link #LOCATION_MODE_NO_CHANGE}.
+     *
+     * <p>This API is normally only useful for components that provide location features.
+     *
+     * @see #isPowerSaveMode()
+     * @see #ACTION_POWER_SAVE_MODE_CHANGED
+     */
+    @LocationPowerSaveMode
+    public int getLocationPowerSaveMode() {
+        final PowerSaveState powerSaveState = getPowerSaveState(ServiceType.GPS);
+        if (!powerSaveState.globalBatterySaverEnabled) {
+            return LOCATION_MODE_NO_CHANGE;
+        }
+        return powerSaveState.gpsMode;
+    }
+
+    /**
      * Returns true if the device is currently in idle mode.  This happens when a device
      * has been sitting unused and unmoving for a sufficiently long period of time, so that
      * it decides to go into a lower power-use state.  This may involve things like turning
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 44238df..2197484 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -209,6 +209,35 @@
     public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
 
     /**
+     * Specifies if a user is disallowed from configuring brightness. When device owner sets it,
+     * it'll only be applied on the target(system) user.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>This user restriction has no effect on managed profiles.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_BRIGHTNESS = "no_config_brightness";
+
+    /**
+     * Specifies if ambient display is disallowed for the user.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>This user restriction has no effect on managed profiles.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
+
+    /**
      * Specifies if a user is disallowed from enabling the
      * "Unknown Sources" setting, that allows installation of apps from unknown sources.
      * The default value is <code>false</code>.
@@ -877,6 +906,27 @@
     public static final String DISALLOW_USER_SWITCH = "no_user_switch";
 
     /**
+     * Specifies whether the user can share file / picture / data from the primary user into the
+     * managed profile, either by sending them from the primary side, or by picking up data within
+     * an app in the managed profile.
+     * <p>
+     * When a managed profile is created, the system allows the user to send data from the primary
+     * side to the profile by setting up certain default cross profile intent filters. If
+     * this is undesired, this restriction can be set to disallow it. Note that this restriction
+     * will not block any sharing allowed by explicit
+     * {@link DevicePolicyManager#addCrossProfileIntentFilter} calls by the profile owner.
+     * <p>
+     * This restriction is only meaningful when set by profile owner. When it is set by device
+     * owner, it does not have any effect.
+     * <p>
+     * The default value is <code>false</code>.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile";
+    /**
      * Application restriction key that is used to indicate the pending arrival
      * of real restrictions for the app.
      *
@@ -2194,6 +2244,12 @@
         }
     }
 
+    /** @removed */
+    @Deprecated
+    public boolean trySetQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) {
+        return requestQuietModeEnabled(enableQuietMode, userHandle);
+    }
+
     /**
      * Enables or disables quiet mode for a managed profile. If quiet mode is enabled, apps in a
      * managed profile don't run, generate notifications, or consume data or battery.
@@ -2219,22 +2275,22 @@
      *
      * @see #isQuietModeEnabled(UserHandle)
      */
-    public boolean trySetQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) {
-        return trySetQuietModeEnabled(enableQuietMode, userHandle, null);
+    public boolean requestQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) {
+        return requestQuietModeEnabled(enableQuietMode, userHandle, null);
     }
 
     /**
-     * Similar to {@link #trySetQuietModeEnabled(boolean, UserHandle)}, except you can specify
+     * Similar to {@link #requestQuietModeEnabled(boolean, UserHandle)}, except you can specify
      * a target to start when user is unlocked. If {@code target} is specified, caller must have
      * the {@link android.Manifest.permission#MANAGE_USERS} permission.
      *
-     * @see {@link #trySetQuietModeEnabled(boolean, UserHandle)}
+     * @see {@link #requestQuietModeEnabled(boolean, UserHandle)}
      * @hide
      */
-    public boolean trySetQuietModeEnabled(
+    public boolean requestQuietModeEnabled(
             boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target) {
         try {
-            return mService.trySetQuietModeEnabled(
+            return mService.requestQuietModeEnabled(
                     mContext.getPackageName(), enableQuietMode, userHandle.getIdentifier(), target);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 340f3fb..12a495b 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -76,8 +76,8 @@
     /**
      * @return a list of VNDK snapshots supported by the framework, as
      * specified in framework manifest. For example,
-     * [("25.0.5", ["libjpeg.so", "libbase.so"]),
-     *  ("25.1.3", ["libjpeg.so", "libbase.so"])]
+     * [("27", ["libjpeg.so", "libbase.so"]),
+     *  ("28", ["libjpeg.so", "libbase.so"])]
      */
     public static native Map<String, String[]> getVndkSnapshots();
 }
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 070b8c1..839a8bf 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -394,4 +394,32 @@
         parcel.writeString(mFsUuid);
         parcel.writeString(mState);
     }
+
+    /** {@hide} */
+    public static final class ScopedAccessProviderContract {
+
+        private ScopedAccessProviderContract() {
+            throw new UnsupportedOperationException("contains constants only");
+        }
+
+        public static final String AUTHORITY = "com.android.documentsui.scopedAccess";
+
+        public static final String TABLE_PACKAGES = "packages";
+        public static final String TABLE_PERMISSIONS = "permissions";
+
+        public static final String COL_PACKAGE = "package_name";
+        public static final String COL_VOLUME_UUID = "volume_uuid";
+        public static final String COL_DIRECTORY = "directory";
+        public static final String COL_GRANTED = "granted";
+
+        public static final String[] TABLE_PACKAGES_COLUMNS = new String[] { COL_PACKAGE };
+        public static final String[] TABLE_PERMISSIONS_COLUMNS =
+                new String[] { COL_PACKAGE, COL_VOLUME_UUID, COL_DIRECTORY, COL_GRANTED };
+
+        public static final int TABLE_PACKAGES_COL_PACKAGE = 0;
+        public static final int TABLE_PERMISSIONS_COL_PACKAGE = 0;
+        public static final int TABLE_PERMISSIONS_COL_VOLUME_UUID = 1;
+        public static final int TABLE_PERMISSIONS_COL_DIRECTORY = 2;
+        public static final int TABLE_PERMISSIONS_COL_GRANTED = 3;
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 66d1bba..850aedd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6802,6 +6802,37 @@
         public static final String ASSIST_DISCLOSURE_ENABLED = "assist_disclosure_enabled";
 
         /**
+         * Control if rotation suggestions are sent to System UI when in rotation locked mode.
+         * Done to enable screen rotation while the the screen rotation is locked. Enabling will
+         * poll the accelerometer in rotation locked mode.
+         *
+         * If 0, then rotation suggestions are not sent to System UI. If 1, suggestions are sent.
+         *
+         * @hide
+         */
+
+        public static final String SHOW_ROTATION_SUGGESTIONS = "show_rotation_suggestions";
+
+        /**
+         * The disabled state of SHOW_ROTATION_SUGGESTIONS.
+         * @hide
+         */
+        public static final int SHOW_ROTATION_SUGGESTIONS_DISABLED = 0x0;
+
+        /**
+         * The enabled state of SHOW_ROTATION_SUGGESTIONS.
+         * @hide
+         */
+        public static final int SHOW_ROTATION_SUGGESTIONS_ENABLED = 0x1;
+
+        /**
+         * The default state of SHOW_ROTATION_SUGGESTIONS.
+         * @hide
+         */
+        public static final int SHOW_ROTATION_SUGGESTIONS_DEFAULT =
+                SHOW_ROTATION_SUGGESTIONS_DISABLED;
+
+        /**
          * Read only list of the service components that the current user has explicitly allowed to
          * see and assist with all of the user's notifications.
          *
@@ -9622,6 +9653,17 @@
         public static final String ALWAYS_ON_DISPLAY_CONSTANTS = "always_on_display_constants";
 
         /**
+        * System VDSO global setting. This links to the "sys.vdso" system property.
+        * The following values are supported:
+        * false  -> both 32 and 64 bit vdso disabled
+        * 32     -> 32 bit vdso enabled
+        * 64     -> 64 bit vdso enabled
+        * Any other value defaults to both 32 bit and 64 bit true.
+        * @hide
+        */
+        public static final String SYS_VDSO = "sys_vdso";
+
+        /**
          * App standby (app idle) specific settings.
          * This is encoded as a key=value list, separated by commas. Ex:
          * <p>
@@ -10027,6 +10069,7 @@
          * If 1 low power mode is enabled.
          * @hide
          */
+        @TestApi
         public static final String LOW_POWER_MODE = "low_power";
 
         /**
@@ -11248,6 +11291,15 @@
          */
         public static final String ENABLE_GNSS_RAW_MEAS_FULL_TRACKING =
                 "enable_gnss_raw_meas_full_tracking";
+
+        /**
+         * Whether we've enabled zram on this device. Takes effect on
+         * reboot. The value "1" enables zram; "0" disables it, and
+         * everything else is unspecified.
+         * @hide
+         */
+        public static final String ZRAM_ENABLED =
+                "zram_enabled";
     }
 
     /**
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index 70de9ee..c568b6f 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -106,9 +106,12 @@
 
     /**
      * Broadcast intent to inform a new visual voicemail SMS has been received. This intent will
-     * only be delivered to the telephony service. {@link #EXTRA_VOICEMAIL_SMS} will be included.
-     */
-    /** @hide */
+     * only be delivered to the telephony service.
+     *
+     * @see #EXTRA_VOICEMAIL_SMS
+     * @see #EXTRA_TARGET_PACKAGE
+     *
+     * @hide */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_VOICEMAIL_SMS_RECEIVED =
             "com.android.internal.provider.action.VOICEMAIL_SMS_RECEIVED";
@@ -121,6 +124,19 @@
     public static final String EXTRA_VOICEMAIL_SMS = "android.provider.extra.VOICEMAIL_SMS";
 
     /**
+     * Extra in {@link #ACTION_VOICEMAIL_SMS_RECEIVED} indicating the target package to bind {@link
+     * android.telephony.VisualVoicemailService}.
+     *
+     * <p>This extra should be set to android.telephony.VisualVoicemailSmsFilterSettings#packageName
+     * while performing filtering. Since the default dialer might change between the filter sending
+     * it and telephony binding to the service, this ensures the service will not receive SMS
+     * filtered by the previous app.
+     *
+     * @hide
+     */
+    public static final String EXTRA_TARGET_PACKAGE = "android.provider.extra.TARGET_PACAKGE";
+
+    /**
      * Extra included in {@link Intent#ACTION_PROVIDER_CHANGED} broadcast intents to indicate if the
      * receiving package made this change.
      */
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/core/java/android/security/keystore/EntryRecoveryData.aidl
similarity index 88%
copy from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
copy to core/java/android/security/keystore/EntryRecoveryData.aidl
index bd76051..c6c20e3 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/core/java/android/security/keystore/EntryRecoveryData.aidl
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.security.recoverablekeystore;
+package android.security.keystore;
 
 /* @hide */
-parcelable KeyStoreRecoveryData;
+parcelable EntryRecoveryData;
diff --git a/core/java/android/security/keystore/EntryRecoveryData.java b/core/java/android/security/keystore/EntryRecoveryData.java
new file mode 100644
index 0000000..aaca3fe
--- /dev/null
+++ b/core/java/android/security/keystore/EntryRecoveryData.java
@@ -0,0 +1,144 @@
+/*
+ * 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 android.security.keystore;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Helper class with data necessary recover a single application key, given a recovery key.
+ *
+ * <ul>
+ *   <li>Alias - Keystore alias of the key.
+ *   <li>Encrypted key material.
+ * </ul>
+ *
+ * Note that Application info is not included. Recovery Agent can only make its own keys
+ * recoverable.
+ *
+ * @hide
+ */
+public final class EntryRecoveryData implements Parcelable {
+    private String mAlias;
+    // The only supported format is AES-256 symmetric key.
+    private byte[] mEncryptedKeyMaterial;
+
+    /**
+     * Builder for creating {@link EntryRecoveryData}.
+     */
+    public static class Builder {
+        private EntryRecoveryData mInstance = new EntryRecoveryData();
+
+        /**
+         * Sets Application-specific alias of the key.
+         *
+         * @param alias The alias.
+         * @return This builder.
+         */
+        public Builder setAlias(@NonNull String alias) {
+            mInstance.mAlias = alias;
+            return this;
+        }
+
+        /**
+         * Sets key material encrypted by recovery key.
+         *
+         * @param encryptedKeyMaterial The key material
+         * @return This builder
+         */
+
+        public Builder setEncryptedKeyMaterial(@NonNull byte[] encryptedKeyMaterial) {
+            mInstance.mEncryptedKeyMaterial = encryptedKeyMaterial;
+            return this;
+        }
+
+        /**
+         * Creates a new {@link EntryRecoveryData} instance.
+         *
+         * @return new instance
+         * @throws NullPointerException if some required fields were not set.
+         */
+        public @NonNull EntryRecoveryData build() {
+            Preconditions.checkNotNull(mInstance.mAlias);
+            Preconditions.checkNotNull(mInstance.mEncryptedKeyMaterial);
+            return mInstance;
+        }
+    }
+
+    private EntryRecoveryData() {
+
+    }
+
+    /**
+     * Deprecated - consider using Builder.
+     * @hide
+     */
+    public EntryRecoveryData(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) {
+        mAlias = Preconditions.checkNotNull(alias);
+        mEncryptedKeyMaterial = Preconditions.checkNotNull(encryptedKeyMaterial);
+    }
+
+    /**
+     * Application-specific alias of the key.
+     *
+     * @see java.security.KeyStore.aliases
+     */
+    public @NonNull String getAlias() {
+        return mAlias;
+    }
+
+    /** Key material encrypted by recovery key. */
+    public @NonNull byte[] getEncryptedKeyMaterial() {
+        return mEncryptedKeyMaterial;
+    }
+
+    public static final Parcelable.Creator<EntryRecoveryData> CREATOR =
+            new Parcelable.Creator<EntryRecoveryData>() {
+                public EntryRecoveryData createFromParcel(Parcel in) {
+                    return new EntryRecoveryData(in);
+                }
+
+                public EntryRecoveryData[] newArray(int length) {
+                    return new EntryRecoveryData[length];
+                }
+            };
+
+    /**
+     * @hide
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mAlias);
+        out.writeByteArray(mEncryptedKeyMaterial);
+    }
+
+    /**
+     * @hide
+     */
+    protected EntryRecoveryData(Parcel in) {
+        mAlias = in.readString();
+        mEncryptedKeyMaterial = in.createByteArray();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/core/java/android/security/keystore/KeyDerivationParams.aidl
similarity index 88%
copy from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
copy to core/java/android/security/keystore/KeyDerivationParams.aidl
index bd76051..f39aa04 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/core/java/android/security/keystore/KeyDerivationParams.aidl
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.security.recoverablekeystore;
+package android.security.keystore;
 
 /* @hide */
-parcelable KeyStoreRecoveryData;
+parcelable KeyDerivationParams;
diff --git a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java b/core/java/android/security/keystore/KeyDerivationParams.java
similarity index 68%
rename from core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
rename to core/java/android/security/keystore/KeyDerivationParams.java
index d162455..b702acc 100644
--- a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
+++ b/core/java/android/security/keystore/KeyDerivationParams.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.security.recoverablekeystore;
+package android.security.keystore;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -28,21 +28,17 @@
 
 /**
  * Collection of parameters which define a key derivation function.
- * Supports
+ * Currently only supports salted SHA-256
  *
- * <ul>
- * <li>SHA256
- * <li>Argon2id
- * </ul>
  * @hide
  */
-public final class KeyDerivationParameters implements Parcelable {
+public final class KeyDerivationParams implements Parcelable {
     private final int mAlgorithm;
     private byte[] mSalt;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({ALGORITHM_SHA256, ALGORITHM_ARGON2ID})
+    @IntDef(prefix = {"ALGORITHM_"}, value = {ALGORITHM_SHA256, ALGORITHM_ARGON2ID})
     public @interface KeyDerivationAlgorithm {
     }
 
@@ -53,6 +49,7 @@
 
     /**
      * Argon2ID
+     * @hide
      */
     // TODO: add Argon2ID support.
     public static final int ALGORITHM_ARGON2ID = 2;
@@ -60,11 +57,11 @@
     /**
      * Creates instance of the class to to derive key using salted SHA256 hash.
      */
-    public static KeyDerivationParameters createSha256Parameters(@NonNull byte[] salt) {
-        return new KeyDerivationParameters(ALGORITHM_SHA256, salt);
+    public static KeyDerivationParams createSha256Params(@NonNull byte[] salt) {
+        return new KeyDerivationParams(ALGORITHM_SHA256, salt);
     }
 
-    private KeyDerivationParameters(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) {
+    private KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) {
         mAlgorithm = algorithm;
         mSalt = Preconditions.checkNotNull(salt);
     }
@@ -83,24 +80,30 @@
         return mSalt;
     }
 
-    public static final Parcelable.Creator<KeyDerivationParameters> CREATOR =
-            new Parcelable.Creator<KeyDerivationParameters>() {
-        public KeyDerivationParameters createFromParcel(Parcel in) {
-                return new KeyDerivationParameters(in);
+    public static final Parcelable.Creator<KeyDerivationParams> CREATOR =
+            new Parcelable.Creator<KeyDerivationParams>() {
+        public KeyDerivationParams createFromParcel(Parcel in) {
+                return new KeyDerivationParams(in);
         }
 
-        public KeyDerivationParameters[] newArray(int length) {
-            return new KeyDerivationParameters[length];
+        public KeyDerivationParams[] newArray(int length) {
+            return new KeyDerivationParams[length];
         }
     };
 
+    /**
+     * @hide
+     */
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mAlgorithm);
         out.writeByteArray(mSalt);
     }
 
-    protected KeyDerivationParameters(Parcel in) {
+    /**
+     * @hide
+     */
+    protected KeyDerivationParams(Parcel in) {
         mAlgorithm = in.readInt();
         mSalt = in.createByteArray();
     }
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/core/java/android/security/keystore/RecoveryData.aidl
similarity index 88%
rename from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
rename to core/java/android/security/keystore/RecoveryData.aidl
index bd76051..4200de1 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/core/java/android/security/keystore/RecoveryData.aidl
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.security.recoverablekeystore;
+package android.security.keystore;
 
 /* @hide */
-parcelable KeyStoreRecoveryData;
+parcelable RecoveryData;
diff --git a/core/java/android/security/keystore/RecoveryData.java b/core/java/android/security/keystore/RecoveryData.java
new file mode 100644
index 0000000..897aa18
--- /dev/null
+++ b/core/java/android/security/keystore/RecoveryData.java
@@ -0,0 +1,200 @@
+/*
+ * 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 android.security.keystore;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Helper class which returns data necessary to recover keys.
+ * Contains
+ *
+ * <ul>
+ * <li>Snapshot version.
+ * <li>Recovery metadata with UI and key derivation parameters.
+ * <li>List of application keys encrypted by recovery key.
+ * <li>Encrypted recovery key.
+ * </ul>
+ *
+ * @hide
+ */
+public final class RecoveryData implements Parcelable {
+    private int mSnapshotVersion;
+    private List<RecoveryMetadata> mRecoveryMetadata;
+    private List<EntryRecoveryData> mEntryRecoveryData;
+    private byte[] mEncryptedRecoveryKeyBlob;
+
+    /**
+     * @hide
+     * Deprecated, consider using builder.
+     */
+    public RecoveryData(
+            int snapshotVersion,
+            @NonNull List<RecoveryMetadata> recoveryMetadata,
+            @NonNull List<EntryRecoveryData> entryRecoveryData,
+            @NonNull byte[] encryptedRecoveryKeyBlob) {
+        mSnapshotVersion = snapshotVersion;
+        mRecoveryMetadata =
+                Preconditions.checkCollectionElementsNotNull(recoveryMetadata, "recoveryMetadata");
+        mEntryRecoveryData = Preconditions.checkCollectionElementsNotNull(entryRecoveryData,
+                "entryRecoveryData");
+        mEncryptedRecoveryKeyBlob = Preconditions.checkNotNull(encryptedRecoveryKeyBlob);
+    }
+
+    private RecoveryData() {
+
+    }
+
+    /**
+     * Snapshot version for given account. It is incremented when user secret or list of application
+     * keys changes.
+     */
+    public int getSnapshotVersion() {
+        return mSnapshotVersion;
+    }
+
+    /**
+     * UI and key derivation parameters. Note that combination of secrets may be used.
+     */
+    public @NonNull List<RecoveryMetadata> getRecoveryMetadata() {
+        return mRecoveryMetadata;
+    }
+
+    /**
+     * List of application keys, with key material encrypted by
+     * the recovery key ({@link #getEncryptedRecoveryKeyBlob}).
+     */
+    public @NonNull List<EntryRecoveryData> getEntryRecoveryData() {
+        return mEntryRecoveryData;
+    }
+
+    /**
+     * Recovery key blob, encrypted by user secret and recovery service public key.
+     */
+    public @NonNull byte[] getEncryptedRecoveryKeyBlob() {
+        return mEncryptedRecoveryKeyBlob;
+    }
+
+    public static final Parcelable.Creator<RecoveryData> CREATOR =
+            new Parcelable.Creator<RecoveryData>() {
+        public RecoveryData createFromParcel(Parcel in) {
+            return new RecoveryData(in);
+        }
+
+        public RecoveryData[] newArray(int length) {
+            return new RecoveryData[length];
+        }
+    };
+
+    /**
+     * Builder for creating {@link RecoveryData}.
+     */
+    public static class Builder {
+        private RecoveryData mInstance = new RecoveryData();
+
+        /**
+         * Snapshot version for given account.
+         *
+         * @param snapshotVersion The snapshot version
+         * @return This builder.
+         */
+        public Builder setSnapshotVersion(int snapshotVersion) {
+            mInstance.mSnapshotVersion = snapshotVersion;
+            return this;
+        }
+
+        /**
+         * Sets UI and key derivation parameters
+         *
+         * @param recoveryMetadata The UI and key derivation parameters
+         * @return This builder.
+         */
+        public Builder setRecoveryMetadata(@NonNull List<RecoveryMetadata> recoveryMetadata) {
+            mInstance.mRecoveryMetadata = recoveryMetadata;
+            return this;
+        }
+
+        /**
+         * List of application keys.
+         *
+         * @param entryRecoveryData List of application keys
+         * @return This builder.
+         */
+        public Builder setEntryRecoveryData(List<EntryRecoveryData> entryRecoveryData) {
+            mInstance.mEntryRecoveryData = entryRecoveryData;
+            return this;
+        }
+
+        /**
+         * Sets recovery key blob
+         *
+         * @param encryptedRecoveryKeyBlob The recovery key blob.
+         * @return This builder.
+         */
+        public Builder setEncryptedRecoveryKeyBlob(@NonNull byte[] encryptedRecoveryKeyBlob) {
+            mInstance.mEncryptedRecoveryKeyBlob = encryptedRecoveryKeyBlob;
+            return this;
+        }
+
+
+        /**
+         * Creates a new {@link RecoveryData} instance.
+         *
+         * @return new instance
+         * @throws NullPointerException if some required fields were not set.
+         */
+        public @NonNull RecoveryData build() {
+            Preconditions.checkCollectionElementsNotNull(mInstance.mRecoveryMetadata,
+                    "recoveryMetadata");
+            Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData,
+                    "entryRecoveryData");
+            Preconditions.checkNotNull(mInstance.mEncryptedRecoveryKeyBlob);
+            return mInstance;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mSnapshotVersion);
+        out.writeTypedList(mRecoveryMetadata);
+        out.writeByteArray(mEncryptedRecoveryKeyBlob);
+        out.writeTypedList(mEntryRecoveryData);
+    }
+
+    /**
+     * @hide
+     */
+    protected RecoveryData(Parcel in) {
+        mSnapshotVersion = in.readInt();
+        mRecoveryMetadata = in.createTypedArrayList(RecoveryMetadata.CREATOR);
+        mEncryptedRecoveryKeyBlob = in.createByteArray();
+        mEntryRecoveryData = in.createTypedArrayList(EntryRecoveryData.CREATOR);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/security/keystore/RecoveryManager.java b/core/java/android/security/keystore/RecoveryManager.java
new file mode 100644
index 0000000..99bd284
--- /dev/null
+++ b/core/java/android/security/keystore/RecoveryManager.java
@@ -0,0 +1,406 @@
+/*
+ * 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 android.security.keystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+
+import com.android.internal.widget.ILockSettings;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A wrapper around KeyStore which lets key be exported to trusted hardware on server side and
+ * recovered later.
+ *
+ * @hide
+ */
+public class RecoveryManager {
+
+    /** Key has been successfully synced. */
+    public static final int RECOVERY_STATUS_SYNCED = 0;
+    /** Waiting for recovery agent to sync the key. */
+    public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
+    /** Recovery account is not available. */
+    public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2;
+    /** Key cannot be synced. */
+    public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
+
+    private final ILockSettings mBinder;
+
+    private RecoveryManager(ILockSettings binder) {
+        mBinder = binder;
+    }
+
+    /**
+     * Gets a new instance of the class.
+     */
+    public static RecoveryManager getInstance() {
+        ILockSettings lockSettings =
+                ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
+        return new RecoveryManager(lockSettings);
+    }
+
+    /**
+     * Initializes key recovery service for the calling application. RecoveryManager
+     * randomly chooses one of the keys from the list and keeps it to use for future key export
+     * operations. Collection of all keys in the list must be signed by the provided {@code
+     * rootCertificateAlias}, which must also be present in the list of root certificates
+     * preinstalled on the device. The random selection allows RecoveryManager to select
+     * which of a set of remote recovery service devices will be used.
+     *
+     * <p>In addition, RecoveryManager enforces a delay of three months between
+     * consecutive initialization attempts, to limit the ability of an attacker to often switch
+     * remote recovery devices and significantly increase number of recovery attempts.
+     *
+     * @param rootCertificateAlias alias of a root certificate preinstalled on the device
+     * @param signedPublicKeyList binary blob a list of X509 certificates and signature
+     * @throws RecoveryManagerException if signature is invalid, or key rotation was rate
+     *     limited.
+     * @hide
+     */
+    public void initRecoveryService(
+            @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
+            throws RecoveryManagerException {
+        try {
+            mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Returns data necessary to store all recoverable keys for given account. Key material is
+     * encrypted with user secret and recovery public key.
+     *
+     * @param account specific to Recovery agent.
+     * @return Data necessary to recover keystore.
+     * @hide
+     */
+    public @NonNull RecoveryData getRecoveryData(@NonNull byte[] account)
+            throws RecoveryManagerException {
+        try {
+            RecoveryData recoveryData = mBinder.getRecoveryData(account);
+            return recoveryData;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
+     * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at
+     * most one registered listener at any time.
+     *
+     * @param intent triggered when new snapshot is available. Unregisters listener if the value is
+     *     {@code null}.
+     * @hide
+     */
+    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
+            throws RecoveryManagerException {
+        try {
+            mBinder.setSnapshotCreatedPendingIntent(intent);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot
+     * version. Version zero is used, if no snapshots were created for the account.
+     *
+     * @return Map from recovery agent accounts to snapshot versions.
+     * @see RecoveryData#getSnapshotVersion
+     * @hide
+     */
+    public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
+            throws RecoveryManagerException {
+        try {
+            // IPC doesn't support generic Maps.
+            @SuppressWarnings("unchecked")
+            Map<byte[], Integer> result =
+                    (Map<byte[], Integer>) mBinder.getRecoverySnapshotVersions();
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Server parameters used to generate new recovery key blobs. This value will be included in
+     * {@code RecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
+     * in vaultParams {@link #startRecoverySession}
+     *
+     * @param serverParams included in recovery key blob.
+     * @see #getRecoveryData
+     * @throws RecoveryManagerException If parameters rotation is rate limited.
+     * @hide
+     */
+    public void setServerParams(byte[] serverParams) throws RecoveryManagerException {
+        try {
+            mBinder.setServerParams(serverParams);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Updates recovery status for given keys. It is used to notify keystore that key was
+     * successfully stored on the server or there were an error. Application can check this value
+     * using {@code getRecoveyStatus}.
+     *
+     * @param packageName Application whose recoverable keys' statuses are to be updated.
+     * @param aliases List of application-specific key aliases. If the array is empty, updates the
+     *     status for all existing recoverable keys.
+     * @param status Status specific to recovery agent.
+     */
+    public void setRecoveryStatus(
+            @NonNull String packageName, @Nullable String[] aliases, int status)
+            throws NameNotFoundException, RecoveryManagerException {
+        try {
+            mBinder.setRecoveryStatus(packageName, aliases, status);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Returns a {@code Map} from Application's KeyStore key aliases to their recovery status.
+     * Negative status values are reserved for recovery agent specific codes. List of common codes:
+     *
+     * <ul>
+     *   <li>{@link #RECOVERY_STATUS_SYNCED}
+     *   <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
+     *   <li>{@link #RECOVERY_STATUS_MISSING_ACCOUNT}
+     *   <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
+     * </ul>
+     *
+     * @return {@code Map} from KeyStore alias to recovery status.
+     * @see #setRecoveryStatus
+     * @hide
+     */
+    public Map<String, Integer> getRecoveryStatus()
+            throws RecoveryManagerException {
+        try {
+            // IPC doesn't support generic Maps.
+            @SuppressWarnings("unchecked")
+            Map<String, Integer> result =
+                    (Map<String, Integer>) mBinder.getRecoveryStatus(/*packageName=*/ null);
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
+     * is necessary to recover data.
+     *
+     * @param secretTypes {@link RecoveryMetadata#TYPE_LOCKSCREEN} or {@link
+     *     RecoveryMetadata#TYPE_CUSTOM_PASSWORD}
+     */
+    public void setRecoverySecretTypes(
+            @NonNull @RecoveryMetadata.UserSecretType int[] secretTypes)
+            throws RecoveryManagerException {
+        try {
+            mBinder.setRecoverySecretTypes(secretTypes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is
+     * necessary to generate RecoveryData.
+     *
+     * @return list of recovery secret types
+     * @see RecoveryData
+     */
+    public @NonNull @RecoveryMetadata.UserSecretType int[] getRecoverySecretTypes()
+            throws RecoveryManagerException {
+        try {
+            return mBinder.getRecoverySecretTypes();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Returns a list of recovery secret types, necessary to create a pending recovery snapshot.
+     * When user enters a secret of a pending type {@link #recoverySecretAvailable} should be
+     * called.
+     *
+     * @return list of recovery secret types
+     * @hide
+     */
+    public @NonNull @RecoveryMetadata.UserSecretType int[] getPendingRecoverySecretTypes()
+            throws RecoveryManagerException {
+        try {
+            return mBinder.getPendingRecoverySecretTypes();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Method notifies KeyStore that a user-generated secret is available. This method generates a
+     * symmetric session key which a trusted remote device can use to return a recovery key. Caller
+     * should use {@link RecoveryMetadata#clearSecret} to override the secret value in
+     * memory.
+     *
+     * @param recoverySecret user generated secret together with parameters necessary to regenerate
+     *     it on a new device.
+     * @hide
+     */
+    public void recoverySecretAvailable(@NonNull RecoveryMetadata recoverySecret)
+            throws RecoveryManagerException {
+        try {
+            mBinder.recoverySecretAvailable(recoverySecret);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Initializes recovery session and returns a blob with proof of recovery secrets possession.
+     * The method generates symmetric key for a session, which trusted remote device can use to
+     * return recovery key.
+     *
+     * @param sessionId ID for recovery session.
+     * @param verifierPublicKey Encoded {@code java.security.cert.X509Certificate} with Public key
+     * used to create the recovery blob on the source device.
+     * Keystore will verify the certificate using root of trust.
+     * @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
+     *     Used to limit number of guesses.
+     * @param vaultChallenge Data passed from server for this recovery session and used to prevent
+     *     replay attacks
+     * @param secrets Secrets provided by user, the method only uses type and secret fields.
+     * @return Binary blob with recovery claim. It is encrypted with verifierPublicKey and contains
+     *     a proof of user secrets, session symmetric key and parameters necessary to identify the
+     *     counter with the number of failed recovery attempts.
+     */
+    public @NonNull byte[] startRecoverySession(
+            @NonNull String sessionId,
+            @NonNull byte[] verifierPublicKey,
+            @NonNull byte[] vaultParams,
+            @NonNull byte[] vaultChallenge,
+            @NonNull List<RecoveryMetadata> secrets)
+            throws RecoveryManagerException {
+        try {
+            byte[] recoveryClaim =
+                    mBinder.startRecoverySession(
+                            sessionId,
+                            verifierPublicKey,
+                            vaultParams,
+                            vaultChallenge,
+                            secrets);
+            return recoveryClaim;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Imports keys.
+     *
+     * @param sessionId Id for recovery session, same as in
+     *     {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
+     * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
+     * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
+     *     and session. KeyStore only uses package names from the application info in {@link
+     *     EntryRecoveryData}. Caller is responsibility to perform certificates check.
+     * @return Map from alias to raw key material.
+     */
+    public Map<String, byte[]> recoverKeys(
+            @NonNull String sessionId,
+            @NonNull byte[] recoveryKeyBlob,
+            @NonNull List<EntryRecoveryData> applicationKeys)
+            throws RecoveryManagerException {
+        try {
+            return (Map<String, byte[]>) mBinder.recoverKeys(
+                    sessionId, recoveryKeyBlob, applicationKeys);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Generates a key called {@code alias} and loads it into the recoverable key store. Returns the
+     * raw material of the key.
+     *
+     * @param alias The key alias.
+     * @throws RecoveryManagerException if an error occurred generating and storing the
+     *     key.
+     */
+    public byte[] generateAndStoreKey(@NonNull String alias)
+            throws RecoveryManagerException {
+        try {
+            return mBinder.generateAndStoreKey(alias);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Removes a key called {@code alias} from the recoverable key store.
+     *
+     * @param alias The key alias.
+     */
+    public void removeKey(@NonNull String alias) throws RecoveryManagerException {
+        try {
+            mBinder.removeKey(alias);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoveryManagerException.fromServiceSpecificException(e);
+        }
+    }
+}
diff --git a/core/java/android/security/keystore/RecoveryManagerException.java b/core/java/android/security/keystore/RecoveryManagerException.java
new file mode 100644
index 0000000..344718a
--- /dev/null
+++ b/core/java/android/security/keystore/RecoveryManagerException.java
@@ -0,0 +1,111 @@
+/*
+ * 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 android.security.keystore;
+
+import android.os.ServiceSpecificException;
+
+/**
+ * Exception thrown by {@link RecoveryManager} methods.
+ *
+ * @hide
+ */
+public class RecoveryManagerException extends Exception {
+    /**
+     * Failed because the loader has not been initialized with a recovery public key yet.
+     */
+    public static final int ERROR_UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
+
+    /**
+     * Failed because no snapshot is yet pending to be synced for the user.
+     */
+    public static final int ERROR_NO_SNAPSHOT_PENDING = 21;
+
+    /**
+     * Failed due to an error internal to AndroidKeyStore.
+     */
+    public static final int ERROR_KEYSTORE_INTERNAL_ERROR = 22;
+
+    /**
+     * Failed because the user does not have a lock screen set.
+     */
+    public static final int ERROR_INSECURE_USER = 24;
+
+    /**
+     * Failed because of an internal database error.
+     */
+    public static final int ERROR_DATABASE_ERROR = 25;
+
+    /**
+     * Failed because the provided certificate was not a valid X509 certificate.
+     */
+    public static final int ERROR_BAD_X509_CERTIFICATE = 26;
+
+    /**
+     * Should never be thrown - some algorithm that all AOSP implementations must support is
+     * not available.
+     */
+    public static final int ERROR_UNEXPECTED_MISSING_ALGORITHM = 27;
+
+    /**
+     * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong,
+     * the data has become corrupted, the data has been tampered with, etc.
+     */
+    public static final int ERROR_DECRYPTION_FAILED = 28;
+
+    /**
+     * Rate limit is enforced to prevent using too many trusted remote devices, since each device
+     * can have its own number of user secret guesses allowed.
+     *
+     * @hide
+     */
+    public static final int ERROR_RATE_LIMIT_EXCEEDED = 29;
+
+    private int mErrorCode;
+
+    /**
+     * Creates new {@link #RecoveryManagerException} instance from the error code.
+     *
+     * @param errorCode An error code, as listed at the top of this file.
+     * @param message The associated error message.
+     * @hide
+     */
+    public static RecoveryManagerException fromErrorCode(
+            int errorCode, String message) {
+        return new RecoveryManagerException(errorCode, message);
+    }
+    /**
+     * Creates new {@link #RecoveryManagerException} from {@link
+     * ServiceSpecificException}.
+     *
+     * @param e exception thrown on service side.
+     * @hide
+     */
+    static RecoveryManagerException fromServiceSpecificException(
+            ServiceSpecificException e) throws RecoveryManagerException {
+        throw RecoveryManagerException.fromErrorCode(e.errorCode, e.getMessage());
+    }
+
+    private RecoveryManagerException(int errorCode, String message) {
+        super(message);
+        mErrorCode = errorCode;
+    }
+
+    /** Returns errorCode. */
+    public int getErrorCode() {
+        return mErrorCode;
+    }
+}
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/core/java/android/security/keystore/RecoveryMetadata.aidl
similarity index 88%
copy from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
copy to core/java/android/security/keystore/RecoveryMetadata.aidl
index bd76051..8e342b4 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/core/java/android/security/keystore/RecoveryMetadata.aidl
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.security.recoverablekeystore;
+package android.security.keystore;
 
 /* @hide */
-parcelable KeyStoreRecoveryData;
+parcelable RecoveryMetadata;
diff --git a/core/java/android/security/keystore/RecoveryMetadata.java b/core/java/android/security/keystore/RecoveryMetadata.java
new file mode 100644
index 0000000..3f09455
--- /dev/null
+++ b/core/java/android/security/keystore/RecoveryMetadata.java
@@ -0,0 +1,274 @@
+/*
+ * 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 android.security.keystore;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * Helper class with data necessary to recover Keystore on a new device.
+ * It defines UI shown to the user and a way to derive a cryptographic key from user output.
+ *
+ * @hide
+ */
+public final class RecoveryMetadata implements Parcelable {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({TYPE_LOCKSCREEN, TYPE_CUSTOM_PASSWORD})
+    public @interface UserSecretType {
+    }
+
+    /**
+     * Lockscreen secret is required to recover KeyStore.
+     */
+    public static final int TYPE_LOCKSCREEN = 100;
+
+    /**
+     * Custom passphrase, unrelated to lock screen, is required to recover KeyStore.
+     */
+    public static final int TYPE_CUSTOM_PASSWORD = 101;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({TYPE_PIN, TYPE_PASSWORD, TYPE_PATTERN})
+    public @interface LockScreenUiFormat {
+    }
+
+    /**
+     * Pin with digits only.
+     */
+    public static final int TYPE_PIN = 1;
+
+    /**
+     * Password. String with latin-1 characters only.
+     */
+    public static final int TYPE_PASSWORD = 2;
+
+    /**
+     * Pattern with 3 by 3 grid.
+     */
+    public static final int TYPE_PATTERN = 3;
+
+    @UserSecretType
+    private Integer mUserSecretType;
+
+    @LockScreenUiFormat
+    private Integer mLockScreenUiFormat;
+
+    /**
+     * Parameters of the key derivation function, including algorithm, difficulty, salt.
+     */
+    private KeyDerivationParams mKeyDerivationParams;
+    private byte[] mSecret; // Derived from user secret. The field must have limited visibility.
+
+    /**
+     * @param secret Constructor creates a reference to the secret. Caller must use
+     * @link {#clearSecret} to overwrite its value in memory.
+     * @hide
+     */
+    public RecoveryMetadata(@UserSecretType int userSecretType,
+            @LockScreenUiFormat int lockScreenUiFormat,
+            @NonNull KeyDerivationParams keyDerivationParams,
+            @NonNull byte[] secret) {
+        mUserSecretType = userSecretType;
+        mLockScreenUiFormat = lockScreenUiFormat;
+        mKeyDerivationParams = Preconditions.checkNotNull(keyDerivationParams);
+        mSecret = Preconditions.checkNotNull(secret);
+    }
+
+    private RecoveryMetadata() {
+
+    }
+
+    /**
+     * @see TYPE_LOCKSCREEN
+     * @see TYPE_CUSTOM_PASSWORD
+     */
+    public @UserSecretType int getUserSecretType() {
+        return mUserSecretType;
+    }
+
+    /**
+     * Specifies UX shown to user during recovery.
+     * Default value is {@code TYPE_LOCKSCREEN}
+     *
+     * @see TYPE_PIN
+     * @see TYPE_PASSWORD
+     * @see TYPE_PATTERN
+     */
+    public @LockScreenUiFormat int getLockScreenUiFormat() {
+        return mLockScreenUiFormat;
+    }
+
+    /**
+     * Specifies function used to derive symmetric key from user input
+     * Format is defined in separate util class.
+     */
+    public @NonNull KeyDerivationParams getKeyDerivationParams() {
+        return mKeyDerivationParams;
+    }
+
+    /**
+     * Secret derived from user input.
+     * Default value is empty array
+     *
+     * @return secret or empty array
+     */
+    public @NonNull byte[] getSecret() {
+        return mSecret;
+    }
+
+    /**
+     * Builder for creating {@link RecoveryMetadata}.
+     */
+    public static class Builder {
+        private RecoveryMetadata mInstance = new RecoveryMetadata();
+
+        /**
+         * Sets user secret type.
+         *
+         * @see TYPE_LOCKSCREEN
+         * @see TYPE_CUSTOM_PASSWORD
+         * @param userSecretType The secret type
+         * @return This builder.
+         */
+        public Builder setUserSecretType(@UserSecretType int userSecretType) {
+            mInstance.mUserSecretType = userSecretType;
+            return this;
+        }
+
+        /**
+         * Sets UI format.
+         *
+         * @see TYPE_PIN
+         * @see TYPE_PASSWORD
+         * @see TYPE_PATTERN
+         * @param lockScreenUiFormat The UI format
+         * @return This builder.
+         */
+        public Builder setLockScreenUiFormat(@LockScreenUiFormat int lockScreenUiFormat) {
+            mInstance.mLockScreenUiFormat = lockScreenUiFormat;
+            return this;
+        }
+
+        /**
+         * Sets parameters of the key derivation function.
+         *
+         * @param keyDerivationParams Key derivation Params
+         * @return This builder.
+         */
+        public Builder setKeyDerivationParams(@NonNull KeyDerivationParams
+                keyDerivationParams) {
+            mInstance.mKeyDerivationParams = keyDerivationParams;
+            return this;
+        }
+
+        /**
+         * Secret derived from user input, or empty array.
+         *
+         * @param secret The secret.
+         * @return This builder.
+         */
+        public Builder setSecret(@NonNull byte[] secret) {
+            mInstance.mSecret = secret;
+            return this;
+        }
+
+
+        /**
+         * Creates a new {@link RecoveryMetadata} instance.
+         * The instance will include default values, if {@link setSecret}
+         * or {@link setUserSecretType} were not called.
+         *
+         * @return new instance
+         * @throws NullPointerException if some required fields were not set.
+         */
+        public @NonNull RecoveryMetadata build() {
+            if (mInstance.mUserSecretType == null) {
+                mInstance.mUserSecretType = TYPE_LOCKSCREEN;
+            }
+            Preconditions.checkNotNull(mInstance.mLockScreenUiFormat);
+            Preconditions.checkNotNull(mInstance.mKeyDerivationParams);
+            if (mInstance.mSecret == null) {
+                mInstance.mSecret = new byte[]{};
+            }
+            return mInstance;
+        }
+    }
+
+    /**
+     * Removes secret from memory than object is no longer used.
+     * Since finalizer call is not reliable, please use @link {#clearSecret} directly.
+     */
+    @Override
+    protected void finalize() throws Throwable {
+        clearSecret();
+        super.finalize();
+    }
+
+    /**
+     * Fills mSecret with zeroes.
+     */
+    public void clearSecret() {
+        Arrays.fill(mSecret, (byte) 0);
+    }
+
+    public static final Parcelable.Creator<RecoveryMetadata> CREATOR =
+            new Parcelable.Creator<RecoveryMetadata>() {
+        public RecoveryMetadata createFromParcel(Parcel in) {
+            return new RecoveryMetadata(in);
+        }
+
+        public RecoveryMetadata[] newArray(int length) {
+            return new RecoveryMetadata[length];
+        }
+    };
+
+    /**
+     * @hide
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mUserSecretType);
+        out.writeInt(mLockScreenUiFormat);
+        out.writeTypedObject(mKeyDerivationParams, flags);
+        out.writeByteArray(mSecret);
+    }
+
+    /**
+     * @hide
+     */
+    protected RecoveryMetadata(Parcel in) {
+        mUserSecretType = in.readInt();
+        mLockScreenUiFormat = in.readInt();
+        mKeyDerivationParams = in.readTypedObject(KeyDerivationParams.CREATOR);
+        mSecret = in.createByteArray();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.aidl b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.aidl
deleted file mode 100644
index fe13179..0000000
--- a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.aidl
+++ /dev/null
@@ -1,20 +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 android.security.recoverablekeystore;
-
-/* @hide */
-parcelable KeyDerivationParameters;
diff --git a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl b/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl
deleted file mode 100644
index 1674e51..0000000
--- a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl
+++ /dev/null
@@ -1,20 +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 android.security.recoverablekeystore;
-
-/* @hide */
-parcelable KeyEntryRecoveryData;
diff --git a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.java b/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.java
deleted file mode 100644
index 5f56c91..0000000
--- a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.java
+++ /dev/null
@@ -1,88 +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 android.security.recoverablekeystore;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-/**
- * Helper class with data necessary recover a single application key, given a recovery key.
- *
- * <ul>
- *   <li>Alias - Keystore alias of the key.
- *   <li>Encrypted key material.
- * </ul>
- *
- * Note that Application info is not included. Recovery Agent can only make its own keys
- * recoverable.
- *
- * @hide
- */
-public final class KeyEntryRecoveryData implements Parcelable {
-    private final String mAlias;
-    // The only supported format is AES-256 symmetric key.
-    private final byte[] mEncryptedKeyMaterial;
-
-    public KeyEntryRecoveryData(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) {
-        mAlias = Preconditions.checkNotNull(alias);
-        mEncryptedKeyMaterial = Preconditions.checkNotNull(encryptedKeyMaterial);
-    }
-
-    /**
-     * Application-specific alias of the key.
-     *
-     * @see java.security.KeyStore.aliases
-     */
-    public @NonNull String getAlias() {
-        return mAlias;
-    }
-
-    /** Encrypted key material encrypted by recovery key. */
-    public @NonNull byte[] getEncryptedKeyMaterial() {
-        return mEncryptedKeyMaterial;
-    }
-
-    public static final Parcelable.Creator<KeyEntryRecoveryData> CREATOR =
-            new Parcelable.Creator<KeyEntryRecoveryData>() {
-                public KeyEntryRecoveryData createFromParcel(Parcel in) {
-                    return new KeyEntryRecoveryData(in);
-                }
-
-                public KeyEntryRecoveryData[] newArray(int length) {
-                    return new KeyEntryRecoveryData[length];
-                }
-            };
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeString(mAlias);
-        out.writeByteArray(mEncryptedKeyMaterial);
-    }
-
-    protected KeyEntryRecoveryData(Parcel in) {
-        mAlias = in.readString();
-        mEncryptedKeyMaterial = in.createByteArray();
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.java b/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.java
deleted file mode 100644
index 087f7a2..0000000
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.java
+++ /dev/null
@@ -1,115 +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 android.security.recoverablekeystore;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.List;
-
-/**
- * Helper class which returns data necessary to recover keys.
- * Contains
- *
- * <ul>
- * <li>Snapshot version.
- * <li>Recovery metadata with UI and key derivation parameters.
- * <li>List of application keys encrypted by recovery key.
- * <li>Encrypted recovery key.
- * </ul>
- *
- * @hide
- */
-public final class KeyStoreRecoveryData implements Parcelable {
-    private final int mSnapshotVersion;
-    private final List<KeyStoreRecoveryMetadata> mRecoveryMetadata;
-    private final List<KeyEntryRecoveryData> mApplicationKeyBlobs;
-    private final byte[] mEncryptedRecoveryKeyBlob;
-
-    public KeyStoreRecoveryData(int snapshotVersion, @NonNull List<KeyStoreRecoveryMetadata>
-            recoveryMetadata, @NonNull List<KeyEntryRecoveryData> applicationKeyBlobs,
-            @NonNull byte[] encryptedRecoveryKeyBlob) {
-        mSnapshotVersion = snapshotVersion;
-        mRecoveryMetadata = Preconditions.checkNotNull(recoveryMetadata);
-        mApplicationKeyBlobs = Preconditions.checkNotNull(applicationKeyBlobs);
-        mEncryptedRecoveryKeyBlob = Preconditions.checkNotNull(encryptedRecoveryKeyBlob);
-    }
-
-    /**
-     * Snapshot version for given account. It is incremented when user secret or list of application
-     * keys changes.
-     */
-    public int getSnapshotVersion() {
-        return mSnapshotVersion;
-    }
-
-    /**
-     * UI and key derivation parameters. Note that combination of secrets may be used.
-     */
-    public @NonNull List<KeyStoreRecoveryMetadata> getRecoveryMetadata() {
-        return mRecoveryMetadata;
-    }
-
-    /**
-     * List of application keys, with key material encrypted by
-     * the recovery key ({@link #getEncryptedRecoveryKeyBlob}).
-     */
-    public @NonNull List<KeyEntryRecoveryData> getApplicationKeyBlobs() {
-        return mApplicationKeyBlobs;
-    }
-
-    /**
-     * Recovery key blob, encrypted by user secret and recovery service public key.
-     */
-    public @NonNull byte[] getEncryptedRecoveryKeyBlob() {
-        return mEncryptedRecoveryKeyBlob;
-    }
-
-    public static final Parcelable.Creator<KeyStoreRecoveryData> CREATOR =
-            new Parcelable.Creator<KeyStoreRecoveryData>() {
-        public KeyStoreRecoveryData createFromParcel(Parcel in) {
-            return new KeyStoreRecoveryData(in);
-        }
-
-        public KeyStoreRecoveryData[] newArray(int length) {
-            return new KeyStoreRecoveryData[length];
-        }
-    };
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(mSnapshotVersion);
-        out.writeTypedList(mRecoveryMetadata);
-        out.writeByteArray(mEncryptedRecoveryKeyBlob);
-        out.writeTypedList(mApplicationKeyBlobs);
-    }
-
-    protected KeyStoreRecoveryData(Parcel in) {
-        mSnapshotVersion = in.readInt();
-        mRecoveryMetadata = in.createTypedArrayList(KeyStoreRecoveryMetadata.CREATOR);
-        mEncryptedRecoveryKeyBlob = in.createByteArray();
-        mApplicationKeyBlobs = in.createTypedArrayList(KeyEntryRecoveryData.CREATOR);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.aidl b/core/java/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.aidl
deleted file mode 100644
index e1d49de..0000000
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.aidl
+++ /dev/null
@@ -1,20 +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 android.security.recoverablekeystore;
-
-/* @hide */
-parcelable KeyStoreRecoveryMetadata;
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.java b/core/java/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.java
deleted file mode 100644
index 43f9c80..0000000
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.java
+++ /dev/null
@@ -1,180 +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 android.security.recoverablekeystore;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-
-/**
- * Helper class with data necessary to recover Keystore on a new device.
- * It defines UI shown to the user and a way to derive a cryptographic key from user output.
- *
- * @hide
- */
-public final class KeyStoreRecoveryMetadata implements Parcelable {
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({TYPE_LOCKSCREEN, TYPE_CUSTOM_PASSWORD})
-    public @interface UserSecretType {
-    }
-
-    /**
-     * Lockscreen secret is required to recover KeyStore.
-     */
-    public static final int TYPE_LOCKSCREEN = 1;
-
-    /**
-     * Custom passphrase, unrelated to lock screen, is required to recover KeyStore.
-     */
-    public static final int TYPE_CUSTOM_PASSWORD = 2;
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({TYPE_PIN, TYPE_PASSWORD, TYPE_PATTERN})
-    public @interface LockScreenUiFormat {
-    }
-
-    /**
-     * Pin with digits only.
-     */
-    public static final int TYPE_PIN = 1;
-
-    /**
-     * Password. String with latin-1 characters only.
-     */
-    public static final int TYPE_PASSWORD = 2;
-
-    /**
-     * Pattern with 3 by 3 grid.
-     */
-    public static final int TYPE_PATTERN = 3;
-
-    @UserSecretType
-    private final int mUserSecretType;
-
-    @LockScreenUiFormat
-    private final int mLockScreenUiFormat;
-
-    /**
-     * Parameters of key derivation function, including algorithm, difficulty, salt.
-     */
-    private KeyDerivationParameters mKeyDerivationParameters;
-    private byte[] mSecret; // Derived from user secret. The field must have limited visibility.
-
-    /**
-     * @param secret Constructor creates a reference to the secret. Caller must use
-     * @link {#clearSecret} to overwrite its value in memory.
-     */
-    public KeyStoreRecoveryMetadata(@UserSecretType int userSecretType,
-            @LockScreenUiFormat int lockScreenUiFormat,
-            @NonNull KeyDerivationParameters keyDerivationParameters, @NonNull byte[] secret) {
-        mUserSecretType = userSecretType;
-        mLockScreenUiFormat = lockScreenUiFormat;
-        mKeyDerivationParameters = Preconditions.checkNotNull(keyDerivationParameters);
-        mSecret = Preconditions.checkNotNull(secret);
-    }
-
-    /**
-     * Specifies UX shown to user during recovery.
-     *
-     * @see KeyStore.TYPE_PIN
-     * @see KeyStore.TYPE_PASSWORD
-     * @see KeyStore.TYPE_PATTERN
-     */
-    public @LockScreenUiFormat int getLockScreenUiFormat() {
-        return mLockScreenUiFormat;
-    }
-
-    /**
-     * Specifies function used to derive symmetric key from user input
-     * Format is defined in separate util class.
-     */
-    public @NonNull KeyDerivationParameters getKeyDerivationParameters() {
-        return mKeyDerivationParameters;
-    }
-
-    /**
-     * Secret string derived from user input.
-     */
-    public @NonNull byte[] getSecret() {
-        return mSecret;
-    }
-
-    /**
-     * @see KeyStore.TYPE_LOCKSCREEN
-     * @see KeyStore.TYPE_CUSTOM_PASSWORD
-     */
-    public @UserSecretType int getUserSecretType() {
-        return mUserSecretType;
-    }
-
-    /**
-     * Removes secret from memory than object is no longer used.
-     * Since finalizer call is not reliable, please use @link {#clearSecret} directly.
-     */
-    @Override
-    protected void finalize() throws Throwable {
-        clearSecret();
-        super.finalize();
-    }
-
-    /**
-     * Fills mSecret with zeroes.
-     */
-    public void clearSecret() {
-        Arrays.fill(mSecret, (byte) 0);
-    }
-
-    public static final Parcelable.Creator<KeyStoreRecoveryMetadata> CREATOR =
-            new Parcelable.Creator<KeyStoreRecoveryMetadata>() {
-        public KeyStoreRecoveryMetadata createFromParcel(Parcel in) {
-            return new KeyStoreRecoveryMetadata(in);
-        }
-
-        public KeyStoreRecoveryMetadata[] newArray(int length) {
-            return new KeyStoreRecoveryMetadata[length];
-        }
-    };
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(mUserSecretType);
-        out.writeInt(mLockScreenUiFormat);
-        out.writeTypedObject(mKeyDerivationParameters, flags);
-        out.writeByteArray(mSecret);
-    }
-
-    protected KeyStoreRecoveryMetadata(Parcel in) {
-        mUserSecretType = in.readInt();
-        mLockScreenUiFormat = in.readInt();
-        mKeyDerivationParameters = in.readTypedObject(KeyDerivationParameters.CREATOR);
-        mSecret = in.createByteArray();
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
deleted file mode 100644
index a65330c..0000000
--- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
+++ /dev/null
@@ -1,510 +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 android.security.recoverablekeystore;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.PendingIntent;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceSpecificException;
-import android.security.KeyStore;
-import android.util.AndroidException;
-
-import com.android.internal.widget.ILockSettings;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * A wrapper around KeyStore which lets key be exported to trusted hardware on server side and
- * recovered later.
- *
- * @hide
- */
-public class RecoverableKeyStoreLoader {
-
-    public static final String PERMISSION_RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
-
-    public static final int NO_ERROR = KeyStore.NO_ERROR;
-    public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR;
-
-    /**
-     * Failed because the loader has not been initialized with a recovery public key yet.
-     */
-    public static final int ERROR_UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
-
-    /**
-     * Failed because no snapshot is yet pending to be synced for the user.
-     */
-    public static final int ERROR_NO_SNAPSHOT_PENDING = 21;
-
-    /**
-     * Failed due to an error internal to AndroidKeyStore.
-     */
-    public static final int ERROR_KEYSTORE_INTERNAL_ERROR = 22;
-
-    /**
-     * Failed because the user does not have a lock screen set.
-     */
-    public static final int ERROR_INSECURE_USER = 24;
-
-    /**
-     * Failed because of an internal database error.
-     */
-    public static final int ERROR_DATABASE_ERROR = 25;
-
-    /**
-     * Failed because the provided certificate was not a valid X509 certificate.
-     */
-    public static final int ERROR_BAD_X509_CERTIFICATE = 26;
-
-    /**
-     * Should never be thrown - some algorithm that all AOSP implementations must support is
-     * not available.
-     */
-    public static final int ERROR_UNEXPECTED_MISSING_ALGORITHM = 27;
-
-    /**
-     * The caller is attempting to perform an operation that is not yet fully supported in the API.
-     */
-    public static final int ERROR_NOT_YET_SUPPORTED = 28;
-
-    /**
-     * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong,
-     * the data has become corrupted, the data has been tampered with, etc.
-     */
-    public static final int ERROR_DECRYPTION_FAILED = 29;
-
-    /**
-     * Rate limit is enforced to prevent using too many trusted remote devices, since each device
-     * can have its own number of user secret guesses allowed.
-     *
-     * @hide
-     */
-    public static final int ERROR_RATE_LIMIT_EXCEEDED = 30;
-
-    /** Key has been successfully synced. */
-    public static final int RECOVERY_STATUS_SYNCED = 0;
-    /** Waiting for recovery agent to sync the key. */
-    public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
-    /** Recovery account is not available. */
-    public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2;
-    /** Key cannot be synced. */
-    public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
-
-    private final ILockSettings mBinder;
-
-    private RecoverableKeyStoreLoader(ILockSettings binder) {
-        mBinder = binder;
-    }
-
-    /** @hide */
-    public static RecoverableKeyStoreLoader getInstance() {
-        ILockSettings lockSettings =
-                ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
-        return new RecoverableKeyStoreLoader(lockSettings);
-    }
-
-    /**
-     * Exceptions returned by {@link RecoverableKeyStoreLoader}.
-     *
-     * @hide
-     */
-    public static class RecoverableKeyStoreLoaderException extends AndroidException {
-        private int mErrorCode;
-
-        /**
-         * Creates new {@link #RecoverableKeyStoreLoaderException} instance from the error code.
-         *
-         * @param errorCode An error code, as listed at the top of this file.
-         * @param message The associated error message.
-         * @hide
-         */
-        public static RecoverableKeyStoreLoaderException fromErrorCode(
-                int errorCode, String message) {
-            return new RecoverableKeyStoreLoaderException(errorCode, message);
-        }
-
-        /**
-         * Creates new {@link #RecoverableKeyStoreLoaderException} from {@link
-         * ServiceSpecificException}.
-         *
-         * @param e exception thrown on service side.
-         * @hide
-         */
-        static RecoverableKeyStoreLoaderException fromServiceSpecificException(
-                ServiceSpecificException e) throws RecoverableKeyStoreLoaderException {
-            throw RecoverableKeyStoreLoaderException.fromErrorCode(e.errorCode, e.getMessage());
-        }
-
-        private RecoverableKeyStoreLoaderException(int errorCode, String message) {
-            super(message);
-            mErrorCode = errorCode;
-        }
-
-        /** Returns errorCode. */
-        public int getErrorCode() {
-            return mErrorCode;
-        }
-    }
-
-    /**
-     * Initializes key recovery service for the calling application. RecoverableKeyStoreLoader
-     * randomly chooses one of the keys from the list and keeps it to use for future key export
-     * operations. Collection of all keys in the list must be signed by the provided {@code
-     * rootCertificateAlias}, which must also be present in the list of root certificates
-     * preinstalled on the device. The random selection allows RecoverableKeyStoreLoader to select
-     * which of a set of remote recovery service devices will be used.
-     *
-     * <p>In addition, RecoverableKeyStoreLoader enforces a delay of three months between
-     * consecutive initialization attempts, to limit the ability of an attacker to often switch
-     * remote recovery devices and significantly increase number of recovery attempts.
-     *
-     * @param rootCertificateAlias alias of a root certificate preinstalled on the device
-     * @param signedPublicKeyList binary blob a list of X509 certificates and signature
-     * @throws RecoverableKeyStoreLoaderException if signature is invalid, or key rotation was rate
-     *     limited.
-     * @hide
-     */
-    public void initRecoveryService(
-            @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
-            throws RecoverableKeyStoreLoaderException {
-        try {
-            mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Returns data necessary to store all recoverable keys for given account. Key material is
-     * encrypted with user secret and recovery public key.
-     *
-     * @param account specific to Recovery agent.
-     * @return Data necessary to recover keystore.
-     * @hide
-     */
-    public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
-            throws RecoverableKeyStoreLoaderException {
-        try {
-            KeyStoreRecoveryData recoveryData = mBinder.getRecoveryData(account);
-            return recoveryData;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
-     * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at
-     * most one registered listener at any time.
-     *
-     * @param intent triggered when new snapshot is available. Unregisters listener if the value is
-     *     {@code null}.
-     * @hide
-     */
-    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
-            throws RecoverableKeyStoreLoaderException {
-        try {
-            mBinder.setSnapshotCreatedPendingIntent(intent);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot
-     * version. Version zero is used, if no snapshots were created for the account.
-     *
-     * @return Map from recovery agent accounts to snapshot versions.
-     * @see KeyStoreRecoveryData#getSnapshotVersion
-     * @hide
-     */
-    public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
-            throws RecoverableKeyStoreLoaderException {
-        try {
-            // IPC doesn't support generic Maps.
-            @SuppressWarnings("unchecked")
-            Map<byte[], Integer> result =
-                    (Map<byte[], Integer>) mBinder.getRecoverySnapshotVersions();
-            return result;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Server parameters used to generate new recovery key blobs. This value will be included in
-     * {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
-     * in vaultParams {@link #startRecoverySession}
-     *
-     * @param serverParameters included in recovery key blob.
-     * @see #getRecoveryData
-     * @throws RecoverableKeyStoreLoaderException If parameters rotation is rate limited.
-     * @hide
-     */
-    public void setServerParameters(long serverParameters)
-            throws RecoverableKeyStoreLoaderException {
-        try {
-            mBinder.setServerParameters(serverParameters);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Updates recovery status for given keys. It is used to notify keystore that key was
-     * successfully stored on the server or there were an error. Application can check this value
-     * using {@code getRecoveyStatus}.
-     *
-     * @param packageName Application whose recoverable keys' statuses are to be updated.
-     * @param aliases List of application-specific key aliases. If the array is empty, updates the
-     *     status for all existing recoverable keys.
-     * @param status Status specific to recovery agent.
-     */
-    public void setRecoveryStatus(
-            @NonNull String packageName, @Nullable String[] aliases, int status)
-            throws NameNotFoundException, RecoverableKeyStoreLoaderException {
-        try {
-            mBinder.setRecoveryStatus(packageName, aliases, status);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Returns a {@code Map} from Application's KeyStore key aliases to their recovery status.
-     * Negative status values are reserved for recovery agent specific codes. List of common codes:
-     *
-     * <ul>
-     *   <li>{@link #RECOVERY_STATUS_SYNCED}
-     *   <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
-     *   <li>{@link #RECOVERY_STATUS_MISSING_ACCOUNT}
-     *   <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
-     * </ul>
-     *
-     * @param packageName Application whose recoverable keys' statuses are to be retrieved. if
-     *     {@code null} caller's package will be used.
-     * @return {@code Map} from KeyStore alias to recovery status.
-     * @see #setRecoveryStatus
-     * @hide
-     */
-    public Map<String, Integer> getRecoveryStatus(@Nullable String packageName)
-            throws RecoverableKeyStoreLoaderException {
-        try {
-            // IPC doesn't support generic Maps.
-            @SuppressWarnings("unchecked")
-            Map<String, Integer> result =
-                    (Map<String, Integer>)
-                            mBinder.getRecoveryStatus(packageName);
-            return result;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
-     * is necessary to recover data.
-     *
-     * @param secretTypes {@link KeyStoreRecoveryMetadata#TYPE_LOCKSCREEN} or {@link
-     *     KeyStoreRecoveryMetadata#TYPE_CUSTOM_PASSWORD}
-     */
-    public void setRecoverySecretTypes(
-            @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes)
-            throws RecoverableKeyStoreLoaderException {
-        try {
-            mBinder.setRecoverySecretTypes(secretTypes);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is
-     * necessary to generate KeyStoreRecoveryData.
-     *
-     * @return list of recovery secret types
-     * @see KeyStoreRecoveryData
-     */
-    public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getRecoverySecretTypes()
-            throws RecoverableKeyStoreLoaderException {
-        try {
-            return mBinder.getRecoverySecretTypes();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Returns a list of recovery secret types, necessary to create a pending recovery snapshot.
-     * When user enters a secret of a pending type {@link #recoverySecretAvailable} should be
-     * called.
-     *
-     * @return list of recovery secret types
-     */
-    public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getPendingRecoverySecretTypes()
-            throws RecoverableKeyStoreLoaderException {
-        try {
-            return mBinder.getPendingRecoverySecretTypes();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Method notifies KeyStore that a user-generated secret is available. This method generates a
-     * symmetric session key which a trusted remote device can use to return a recovery key. Caller
-     * should use {@link KeyStoreRecoveryMetadata#clearSecret} to override the secret value in
-     * memory.
-     *
-     * @param recoverySecret user generated secret together with parameters necessary to regenerate
-     *     it on a new device.
-     */
-    public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret)
-            throws RecoverableKeyStoreLoaderException {
-        try {
-            mBinder.recoverySecretAvailable(recoverySecret);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Initializes recovery session and returns a blob with proof of recovery secrets possession.
-     * The method generates symmetric key for a session, which trusted remote device can use to
-     * return recovery key.
-     *
-     * @param sessionId ID for recovery session.
-     * @param verifierPublicKey Certificate with Public key used to create the recovery blob on the
-     *     source device. Keystore will verify the certificate using root of trust.
-     * @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
-     *     Used to limit number of guesses.
-     * @param vaultChallenge Data passed from server for this recovery session and used to prevent
-     *     replay attacks
-     * @param secrets Secrets provided by user, the method only uses type and secret fields.
-     * @return Binary blob with recovery claim. It is encrypted with verifierPublicKey and contains
-     *     a proof of user secrets, session symmetric key and parameters necessary to identify the
-     *     counter with the number of failed recovery attempts.
-     */
-    public @NonNull byte[] startRecoverySession(
-            @NonNull String sessionId,
-            @NonNull byte[] verifierPublicKey,
-            @NonNull byte[] vaultParams,
-            @NonNull byte[] vaultChallenge,
-            @NonNull List<KeyStoreRecoveryMetadata> secrets)
-            throws RecoverableKeyStoreLoaderException {
-        try {
-            byte[] recoveryClaim =
-                    mBinder.startRecoverySession(
-                            sessionId,
-                            verifierPublicKey,
-                            vaultParams,
-                            vaultChallenge,
-                            secrets);
-            return recoveryClaim;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Imports keys.
-     *
-     * @param sessionId Id for recovery session, same as in
-     *     {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
-     * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
-     * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
-     *     and session. KeyStore only uses package names from the application info in {@link
-     *     KeyEntryRecoveryData}. Caller is responsibility to perform certificates check.
-     * @return Map from alias to raw key material.
-     */
-    public Map<String, byte[]> recoverKeys(
-            @NonNull String sessionId,
-            @NonNull byte[] recoveryKeyBlob,
-            @NonNull List<KeyEntryRecoveryData> applicationKeys)
-            throws RecoverableKeyStoreLoaderException {
-        try {
-            return (Map<String, byte[]>) mBinder.recoverKeys(
-                    sessionId, recoveryKeyBlob, applicationKeys);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Generates a key called {@code alias} and loads it into the recoverable key store. Returns the
-     * raw material of the key.
-     *
-     * @param alias The key alias.
-     * @throws RecoverableKeyStoreLoaderException if an error occurred generating and storing the
-     *     key.
-     */
-    public byte[] generateAndStoreKey(@NonNull String alias)
-            throws RecoverableKeyStoreLoaderException {
-        try {
-            return mBinder.generateAndStoreKey(alias);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-
-    /**
-     * Removes a key called {@code alias} from the recoverable key store.
-     *
-     * @param alias The key alias.
-     */
-    public void removeKey(@NonNull String alias) throws RecoverableKeyStoreLoaderException {
-        try {
-            mBinder.removeKey(alias);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } catch (ServiceSpecificException e) {
-            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
-        }
-    }
-}
diff --git a/telephony/java/android/telephony/data/InterfaceAddress.aidl b/core/java/android/service/euicc/EuiccProfileInfo.aidl
similarity index 81%
rename from telephony/java/android/telephony/data/InterfaceAddress.aidl
rename to core/java/android/service/euicc/EuiccProfileInfo.aidl
index d750363..321021b 100644
--- a/telephony/java/android/telephony/data/InterfaceAddress.aidl
+++ b/core/java/android/service/euicc/EuiccProfileInfo.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 The Android Open Source Project
+ * 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.
@@ -13,8 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.service.euicc;
 
-/** @hide */
-package android.telephony.data;
-
-parcelable InterfaceAddress;
+parcelable EuiccProfileInfo;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index bf4b6ac..aa97b2a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1917,10 +1917,10 @@
 
     private static float measurePara(TextPaint paint, CharSequence text, int start, int end,
             TextDirectionHeuristic textDir) {
-        MeasuredText mt = null;
+        MeasuredParagraph mt = null;
         TextLine tl = TextLine.obtain();
         try {
-            mt = MeasuredText.buildForBidi(text, start, end, textDir, mt);
+            mt = MeasuredParagraph.buildForBidi(text, start, end, textDir, mt);
             final char[] chars = mt.getChars();
             final int len = chars.length;
             final Directions directions = mt.getDirections(0, len);
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
new file mode 100644
index 0000000..c93e036
--- /dev/null
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -0,0 +1,677 @@
+/*
+ * Copyright (C) 2010 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.text;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Paint;
+import android.text.AutoGrowArray.ByteArray;
+import android.text.AutoGrowArray.FloatArray;
+import android.text.AutoGrowArray.IntArray;
+import android.text.Layout.Directions;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+import android.util.Pools.SynchronizedPool;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.util.Arrays;
+
+/**
+ * MeasuredParagraph provides text information for rendering purpose.
+ *
+ * The first motivation of this class is identify the text directions and retrieving individual
+ * character widths. However retrieving character widths is slower than identifying text directions.
+ * Thus, this class provides several builder methods for specific purposes.
+ *
+ * - buildForBidi:
+ *   Compute only text directions.
+ * - buildForMeasurement:
+ *   Compute text direction and all character widths.
+ * - buildForStaticLayout:
+ *   This is bit special. StaticLayout also needs to know text direction and character widths for
+ *   line breaking, but all things are done in native code. Similarly, text measurement is done
+ *   in native code. So instead of storing result to Java array, this keeps the result in native
+ *   code since there is no good reason to move the results to Java layer.
+ *
+ * In addition to the character widths, some additional information is computed for each purposes,
+ * e.g. whole text length for measurement or font metrics for static layout.
+ *
+ * MeasuredParagraph is NOT a thread safe object.
+ * @hide
+ */
+public class MeasuredParagraph {
+    private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
+
+    private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+            MeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024);
+
+    private MeasuredParagraph() {}  // Use build static functions instead.
+
+    private static final SynchronizedPool<MeasuredParagraph> sPool = new SynchronizedPool<>(1);
+
+    private static @NonNull MeasuredParagraph obtain() { // Use build static functions instead.
+        final MeasuredParagraph mt = sPool.acquire();
+        return mt != null ? mt : new MeasuredParagraph();
+    }
+
+    /**
+     * Recycle the MeasuredParagraph.
+     *
+     * Do not call any methods after you call this method.
+     */
+    public void recycle() {
+        release();
+        sPool.release(this);
+    }
+
+    // The casted original text.
+    //
+    // This may be null if the passed text is not a Spanned.
+    private @Nullable Spanned mSpanned;
+
+    // The start offset of the target range in the original text (mSpanned);
+    private @IntRange(from = 0) int mTextStart;
+
+    // The length of the target range in the original text.
+    private @IntRange(from = 0) int mTextLength;
+
+    // The copied character buffer for measuring text.
+    //
+    // The length of this array is mTextLength.
+    private @Nullable char[] mCopiedBuffer;
+
+    // The whole paragraph direction.
+    private @Layout.Direction int mParaDir;
+
+    // True if the text is LTR direction and doesn't contain any bidi characters.
+    private boolean mLtrWithoutBidi;
+
+    // The bidi level for individual characters.
+    //
+    // This is empty if mLtrWithoutBidi is true.
+    private @NonNull ByteArray mLevels = new ByteArray();
+
+    // The whole width of the text.
+    // See getWholeWidth comments.
+    private @FloatRange(from = 0.0f) float mWholeWidth;
+
+    // Individual characters' widths.
+    // See getWidths comments.
+    private @Nullable FloatArray mWidths = new FloatArray();
+
+    // The span end positions.
+    // See getSpanEndCache comments.
+    private @Nullable IntArray mSpanEndCache = new IntArray(4);
+
+    // The font metrics.
+    // See getFontMetrics comments.
+    private @Nullable IntArray mFontMetrics = new IntArray(4 * 4);
+
+    // The native MeasuredParagraph.
+    // See getNativePtr comments.
+    // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead.
+    private /* Maybe Zero */ long mNativePtr = 0;
+    private @Nullable Runnable mNativeObjectCleaner;
+
+    // Associate the native object to this Java object.
+    private void bindNativeObject(/* Non Zero*/ long nativePtr) {
+        mNativePtr = nativePtr;
+        mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr);
+    }
+
+    // Decouple the native object from this Java object and release the native object.
+    private void unbindNativeObject() {
+        if (mNativePtr != 0) {
+            mNativeObjectCleaner.run();
+            mNativePtr = 0;
+        }
+    }
+
+    // Following two objects are for avoiding object allocation.
+    private @NonNull TextPaint mCachedPaint = new TextPaint();
+    private @Nullable Paint.FontMetricsInt mCachedFm;
+
+    /**
+     * Releases internal buffers.
+     */
+    public void release() {
+        reset();
+        mLevels.clearWithReleasingLargeArray();
+        mWidths.clearWithReleasingLargeArray();
+        mFontMetrics.clearWithReleasingLargeArray();
+        mSpanEndCache.clearWithReleasingLargeArray();
+    }
+
+    /**
+     * Resets the internal state for starting new text.
+     */
+    private void reset() {
+        mSpanned = null;
+        mCopiedBuffer = null;
+        mWholeWidth = 0;
+        mLevels.clear();
+        mWidths.clear();
+        mFontMetrics.clear();
+        mSpanEndCache.clear();
+        unbindNativeObject();
+    }
+
+    /**
+     * Returns the characters to be measured.
+     *
+     * This is always available.
+     */
+    public @NonNull char[] getChars() {
+        return mCopiedBuffer;
+    }
+
+    /**
+     * Returns the paragraph direction.
+     *
+     * This is always available.
+     */
+    public @Layout.Direction int getParagraphDir() {
+        return mParaDir;
+    }
+
+    /**
+     * Returns the directions.
+     *
+     * This is always available.
+     */
+    public Directions getDirections(@IntRange(from = 0) int start,  // inclusive
+                                    @IntRange(from = 0) int end) {  // exclusive
+        if (mLtrWithoutBidi) {
+            return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+        }
+
+        final int length = end - start;
+        return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start,
+                length);
+    }
+
+    /**
+     * Returns the whole text width.
+     *
+     * This is available only if the MeasureText is computed with computeForMeasurement.
+     * Returns 0 in other cases.
+     */
+    public @FloatRange(from = 0.0f) float getWholeWidth() {
+        return mWholeWidth;
+    }
+
+    /**
+     * Returns the individual character's width.
+     *
+     * This is available only if the MeasureText is computed with computeForMeasurement.
+     * Returns empty array in other cases.
+     */
+    public @NonNull FloatArray getWidths() {
+        return mWidths;
+    }
+
+    /**
+     * Returns the MetricsAffectingSpan end indices.
+     *
+     * If the input text is not a spanned string, this has one value that is the length of the text.
+     *
+     * This is available only if the MeasureText is computed with computeForStaticLayout.
+     * Returns empty array in other cases.
+     */
+    public @NonNull IntArray getSpanEndCache() {
+        return mSpanEndCache;
+    }
+
+    /**
+     * Returns the int array which holds FontMetrics.
+     *
+     * This array holds the repeat of top, bottom, ascent, descent of font metrics value.
+     *
+     * This is available only if the MeasureText is computed with computeForStaticLayout.
+     * Returns empty array in other cases.
+     */
+    public @NonNull IntArray getFontMetrics() {
+        return mFontMetrics;
+    }
+
+    /**
+     * Returns the native ptr of the MeasuredParagraph.
+     *
+     * This is available only if the MeasureText is computed with computeForStaticLayout.
+     * Returns 0 in other cases.
+     */
+    public /* Maybe Zero */ long getNativePtr() {
+        return mNativePtr;
+    }
+
+    /**
+     * Generates new MeasuredParagraph for Bidi computation.
+     *
+     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
+     * result to recycle and returns recycle.
+     *
+     * @param text the character sequence to be measured
+     * @param start the inclusive start offset of the target region in the text
+     * @param end the exclusive end offset of the target region in the text
+     * @param textDir the text direction
+     * @param recycle pass existing MeasuredParagraph if you want to recycle it.
+     *
+     * @return measured text
+     */
+    public static @NonNull MeasuredParagraph buildForBidi(@NonNull CharSequence text,
+                                                     @IntRange(from = 0) int start,
+                                                     @IntRange(from = 0) int end,
+                                                     @NonNull TextDirectionHeuristic textDir,
+                                                     @Nullable MeasuredParagraph recycle) {
+        final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
+        mt.resetAndAnalyzeBidi(text, start, end, textDir);
+        return mt;
+    }
+
+    /**
+     * Generates new MeasuredParagraph for measuring texts.
+     *
+     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
+     * result to recycle and returns recycle.
+     *
+     * @param paint the paint to be used for rendering the text.
+     * @param text the character sequence to be measured
+     * @param start the inclusive start offset of the target region in the text
+     * @param end the exclusive end offset of the target region in the text
+     * @param textDir the text direction
+     * @param recycle pass existing MeasuredParagraph if you want to recycle it.
+     *
+     * @return measured text
+     */
+    public static @NonNull MeasuredParagraph buildForMeasurement(@NonNull TextPaint paint,
+                                                            @NonNull CharSequence text,
+                                                            @IntRange(from = 0) int start,
+                                                            @IntRange(from = 0) int end,
+                                                            @NonNull TextDirectionHeuristic textDir,
+                                                            @Nullable MeasuredParagraph recycle) {
+        final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
+        mt.resetAndAnalyzeBidi(text, start, end, textDir);
+
+        mt.mWidths.resize(mt.mTextLength);
+        if (mt.mTextLength == 0) {
+            return mt;
+        }
+
+        if (mt.mSpanned == null) {
+            // No style change by MetricsAffectingSpan. Just measure all text.
+            mt.applyMetricsAffectingSpan(
+                    paint, null /* spans */, start, end, 0 /* native static layout ptr */);
+        } else {
+            // There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
+            int spanEnd;
+            for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
+                spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class);
+                MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
+                        MetricAffectingSpan.class);
+                spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
+                mt.applyMetricsAffectingSpan(
+                        paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */);
+            }
+        }
+        return mt;
+    }
+
+    /**
+     * Generates new MeasuredParagraph for StaticLayout.
+     *
+     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
+     * result to recycle and returns recycle.
+     *
+     * @param paint the paint to be used for rendering the text.
+     * @param text the character sequence to be measured
+     * @param start the inclusive start offset of the target region in the text
+     * @param end the exclusive end offset of the target region in the text
+     * @param textDir the text direction
+     * @param recycle pass existing MeasuredParagraph if you want to recycle it.
+     *
+     * @return measured text
+     */
+    public static @NonNull MeasuredParagraph buildForStaticLayout(
+            @NonNull TextPaint paint,
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int start,
+            @IntRange(from = 0) int end,
+            @NonNull TextDirectionHeuristic textDir,
+            @Nullable MeasuredParagraph recycle) {
+        final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
+        mt.resetAndAnalyzeBidi(text, start, end, textDir);
+        if (mt.mTextLength == 0) {
+            // Need to build empty native measured text for StaticLayout.
+            // TODO: Stop creating empty measured text for empty lines.
+            long nativeBuilderPtr = nInitBuilder();
+            try {
+                mt.bindNativeObject(
+                        nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
+            } finally {
+                nFreeBuilder(nativeBuilderPtr);
+            }
+            return mt;
+        }
+
+        long nativeBuilderPtr = nInitBuilder();
+        try {
+            if (mt.mSpanned == null) {
+                // No style change by MetricsAffectingSpan. Just measure all text.
+                mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr);
+                mt.mSpanEndCache.append(end);
+            } else {
+                // There may be a MetricsAffectingSpan. Split into span transitions and apply
+                // styles.
+                int spanEnd;
+                for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
+                    spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
+                                                             MetricAffectingSpan.class);
+                    MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
+                            MetricAffectingSpan.class);
+                    spans = TextUtils.removeEmptySpans(spans, mt.mSpanned,
+                                                       MetricAffectingSpan.class);
+                    mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd,
+                                                 nativeBuilderPtr);
+                    mt.mSpanEndCache.append(spanEnd);
+                }
+            }
+            mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
+        } finally {
+            nFreeBuilder(nativeBuilderPtr);
+        }
+
+        return mt;
+    }
+
+    /**
+     * Reset internal state and analyzes text for bidirectional runs.
+     *
+     * @param text the character sequence to be measured
+     * @param start the inclusive start offset of the target region in the text
+     * @param end the exclusive end offset of the target region in the text
+     * @param textDir the text direction
+     */
+    private void resetAndAnalyzeBidi(@NonNull CharSequence text,
+                                     @IntRange(from = 0) int start,  // inclusive
+                                     @IntRange(from = 0) int end,  // exclusive
+                                     @NonNull TextDirectionHeuristic textDir) {
+        reset();
+        mSpanned = text instanceof Spanned ? (Spanned) text : null;
+        mTextStart = start;
+        mTextLength = end - start;
+
+        if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) {
+            mCopiedBuffer = new char[mTextLength];
+        }
+        TextUtils.getChars(text, start, end, mCopiedBuffer, 0);
+
+        // Replace characters associated with ReplacementSpan to U+FFFC.
+        if (mSpanned != null) {
+            ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class);
+
+            for (int i = 0; i < spans.length; i++) {
+                int startInPara = mSpanned.getSpanStart(spans[i]) - start;
+                int endInPara = mSpanned.getSpanEnd(spans[i]) - start;
+                // The span interval may be larger and must be restricted to [start, end)
+                if (startInPara < 0) startInPara = 0;
+                if (endInPara > mTextLength) endInPara = mTextLength;
+                Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER);
+            }
+        }
+
+        if ((textDir == TextDirectionHeuristics.LTR
+                || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
+                || textDir == TextDirectionHeuristics.ANYRTL_LTR)
+                && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
+            mLevels.clear();
+            mParaDir = Layout.DIR_LEFT_TO_RIGHT;
+            mLtrWithoutBidi = true;
+        } else {
+            final int bidiRequest;
+            if (textDir == TextDirectionHeuristics.LTR) {
+                bidiRequest = Layout.DIR_REQUEST_LTR;
+            } else if (textDir == TextDirectionHeuristics.RTL) {
+                bidiRequest = Layout.DIR_REQUEST_RTL;
+            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
+                bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
+            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
+                bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
+            } else {
+                final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
+                bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
+            }
+            mLevels.resize(mTextLength);
+            mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray());
+            mLtrWithoutBidi = false;
+        }
+    }
+
+    private void applyReplacementRun(@NonNull ReplacementSpan replacement,
+                                     @IntRange(from = 0) int start,  // inclusive, in copied buffer
+                                     @IntRange(from = 0) int end,  // exclusive, in copied buffer
+                                     /* Maybe Zero */ long nativeBuilderPtr) {
+        // Use original text. Shouldn't matter.
+        // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for
+        //       backward compatibility? or Should we initialize them for getFontMetricsInt?
+        final float width = replacement.getSize(
+                mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm);
+        if (nativeBuilderPtr == 0) {
+            // Assigns all width to the first character. This is the same behavior as minikin.
+            mWidths.set(start, width);
+            if (end > start + 1) {
+                Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f);
+            }
+            mWholeWidth += width;
+        } else {
+            nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
+                               width);
+        }
+    }
+
+    private void applyStyleRun(@IntRange(from = 0) int start,  // inclusive, in copied buffer
+                               @IntRange(from = 0) int end,  // exclusive, in copied buffer
+                               /* Maybe Zero */ long nativeBuilderPtr) {
+        if (nativeBuilderPtr != 0) {
+            mCachedPaint.getFontMetricsInt(mCachedFm);
+        }
+
+        if (mLtrWithoutBidi) {
+            // If the whole text is LTR direction, just apply whole region.
+            if (nativeBuilderPtr == 0) {
+                mWholeWidth += mCachedPaint.getTextRunAdvances(
+                        mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
+                        mWidths.getRawArray(), start);
+            } else {
+                nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
+                        false /* isRtl */);
+            }
+        } else {
+            // If there is multiple bidi levels, split into individual bidi level and apply style.
+            byte level = mLevels.get(start);
+            // Note that the empty text or empty range won't reach this method.
+            // Safe to search from start + 1.
+            for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) {
+                if (levelEnd == end || mLevels.get(levelEnd) != level) {  // transition point
+                    final boolean isRtl = (level & 0x1) != 0;
+                    if (nativeBuilderPtr == 0) {
+                        final int levelLength = levelEnd - levelStart;
+                        mWholeWidth += mCachedPaint.getTextRunAdvances(
+                                mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
+                                isRtl, mWidths.getRawArray(), levelStart);
+                    } else {
+                        nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart,
+                                levelEnd, isRtl);
+                    }
+                    if (levelEnd == end) {
+                        break;
+                    }
+                    levelStart = levelEnd;
+                    level = mLevels.get(levelEnd);
+                }
+            }
+        }
+    }
+
+    private void applyMetricsAffectingSpan(
+            @NonNull TextPaint paint,
+            @Nullable MetricAffectingSpan[] spans,
+            @IntRange(from = 0) int start,  // inclusive, in original text buffer
+            @IntRange(from = 0) int end,  // exclusive, in original text buffer
+            /* Maybe Zero */ long nativeBuilderPtr) {
+        mCachedPaint.set(paint);
+        // XXX paint should not have a baseline shift, but...
+        mCachedPaint.baselineShift = 0;
+
+        final boolean needFontMetrics = nativeBuilderPtr != 0;
+
+        if (needFontMetrics && mCachedFm == null) {
+            mCachedFm = new Paint.FontMetricsInt();
+        }
+
+        ReplacementSpan replacement = null;
+        if (spans != null) {
+            for (int i = 0; i < spans.length; i++) {
+                MetricAffectingSpan span = spans[i];
+                if (span instanceof ReplacementSpan) {
+                    // The last ReplacementSpan is effective for backward compatibility reasons.
+                    replacement = (ReplacementSpan) span;
+                } else {
+                    // TODO: No need to call updateMeasureState for ReplacementSpan as well?
+                    span.updateMeasureState(mCachedPaint);
+                }
+            }
+        }
+
+        final int startInCopiedBuffer = start - mTextStart;
+        final int endInCopiedBuffer = end - mTextStart;
+
+        if (replacement != null) {
+            applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer,
+                                nativeBuilderPtr);
+        } else {
+            applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr);
+        }
+
+        if (needFontMetrics) {
+            if (mCachedPaint.baselineShift < 0) {
+                mCachedFm.ascent += mCachedPaint.baselineShift;
+                mCachedFm.top += mCachedPaint.baselineShift;
+            } else {
+                mCachedFm.descent += mCachedPaint.baselineShift;
+                mCachedFm.bottom += mCachedPaint.baselineShift;
+            }
+
+            mFontMetrics.append(mCachedFm.top);
+            mFontMetrics.append(mCachedFm.bottom);
+            mFontMetrics.append(mCachedFm.ascent);
+            mFontMetrics.append(mCachedFm.descent);
+        }
+    }
+
+    /**
+     * Returns the maximum index that the accumulated width not exceeds the width.
+     *
+     * If forward=false is passed, returns the minimum index from the end instead.
+     *
+     * This only works if the MeasuredParagraph is computed with computeForMeasurement.
+     * Undefined behavior in other case.
+     */
+    @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) {
+        float[] w = mWidths.getRawArray();
+        if (forwards) {
+            int i = 0;
+            while (i < limit) {
+                width -= w[i];
+                if (width < 0.0f) break;
+                i++;
+            }
+            while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--;
+            return i;
+        } else {
+            int i = limit - 1;
+            while (i >= 0) {
+                width -= w[i];
+                if (width < 0.0f) break;
+                i--;
+            }
+            while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) {
+                i++;
+            }
+            return limit - i - 1;
+        }
+    }
+
+    /**
+     * Returns the length of the substring.
+     *
+     * This only works if the MeasuredParagraph is computed with computeForMeasurement.
+     * Undefined behavior in other case.
+     */
+    @FloatRange(from = 0.0f) float measure(int start, int limit) {
+        float width = 0;
+        float[] w = mWidths.getRawArray();
+        for (int i = start; i < limit; ++i) {
+            width += w[i];
+        }
+        return width;
+    }
+
+    private static native /* Non Zero */ long nInitBuilder();
+
+    /**
+     * Apply style to make native measured text.
+     *
+     * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
+     * @param paintPtr The native paint pointer to be applied.
+     * @param start The start offset in the copied buffer.
+     * @param end The end offset in the copied buffer.
+     * @param isRtl True if the text is RTL.
+     */
+    private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
+                                            /* Non Zero */ long paintPtr,
+                                            @IntRange(from = 0) int start,
+                                            @IntRange(from = 0) int end,
+                                            boolean isRtl);
+
+    /**
+     * Apply ReplacementRun to make native measured text.
+     *
+     * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
+     * @param paintPtr The native paint pointer to be applied.
+     * @param start The start offset in the copied buffer.
+     * @param end The end offset in the copied buffer.
+     * @param width The width of the replacement.
+     */
+    private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr,
+                                                  /* Non Zero */ long paintPtr,
+                                                  @IntRange(from = 0) int start,
+                                                  @IntRange(from = 0) int end,
+                                                  @FloatRange(from = 0) float width);
+
+    private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr,
+                                                 @NonNull char[] text);
+
+    private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
+
+    @CriticalNative
+    private static native /* Non Zero */ long nGetReleaseFunc();
+}
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 14d6f9e..2c30360 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -16,661 +16,255 @@
 
 package android.text;
 
-import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.Paint;
-import android.text.AutoGrowArray.ByteArray;
-import android.text.AutoGrowArray.FloatArray;
-import android.text.AutoGrowArray.IntArray;
-import android.text.Layout.Directions;
-import android.text.style.MetricAffectingSpan;
-import android.text.style.ReplacementSpan;
-import android.util.Pools.SynchronizedPool;
+import android.util.IntArray;
 
-import dalvik.annotation.optimization.CriticalNative;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
 
-import libcore.util.NativeAllocationRegistry;
-
-import java.util.Arrays;
+import java.util.ArrayList;
 
 /**
- * MeasuredText provides text information for rendering purpose.
- *
- * The first motivation of this class is identify the text directions and retrieving individual
- * character widths. However retrieving character widths is slower than identifying text directions.
- * Thus, this class provides several builder methods for specific purposes.
- *
- * - buildForBidi:
- *   Compute only text directions.
- * - buildForMeasurement:
- *   Compute text direction and all character widths.
- * - buildForStaticLayout:
- *   This is bit special. StaticLayout also needs to know text direction and character widths for
- *   line breaking, but all things are done in native code. Similarly, text measurement is done
- *   in native code. So instead of storing result to Java array, this keeps the result in native
- *   code since there is no good reason to move the results to Java layer.
- *
- * In addition to the character widths, some additional information is computed for each purposes,
- * e.g. whole text length for measurement or font metrics for static layout.
- *
- * MeasuredText is NOT a thread safe object.
- * @hide
+ * A text which has already been measured.
  */
-public class MeasuredText {
-    private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
+public class MeasuredText implements Spanned {
+    private static final char LINE_FEED = '\n';
 
-    private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
-            MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024);
+    // The original text.
+    private final @NonNull CharSequence mText;
 
-    private MeasuredText() {}  // Use build static functions instead.
+    // The inclusive start offset of the measuring target.
+    private final @IntRange(from = 0) int mStart;
 
-    private static final SynchronizedPool<MeasuredText> sPool = new SynchronizedPool<>(1);
+    // The exclusive end offset of the measuring target.
+    private final @IntRange(from = 0) int mEnd;
 
-    private static @NonNull MeasuredText obtain() { // Use build static functions instead.
-        final MeasuredText mt = sPool.acquire();
-        return mt != null ? mt : new MeasuredText();
+    // The TextPaint used for measurement.
+    private final @NonNull TextPaint mPaint;
+
+    // The requested text direction.
+    private final @NonNull TextDirectionHeuristic mTextDir;
+
+    // The measured paragraph texts.
+    private final @NonNull MeasuredParagraph[] mMeasuredParagraphs;
+
+    // The sorted paragraph end offsets.
+    private final @NonNull int[] mParagraphBreakPoints;
+
+    /**
+     * Build MeasuredText from the text.
+     *
+     * @param text The text to be measured.
+     * @param paint The paint to be used for drawing.
+     * @param textDir The text direction.
+     * @return The measured text.
+     */
+    public static @NonNull MeasuredText build(@NonNull CharSequence text,
+                                              @NonNull TextPaint paint,
+                                              @NonNull TextDirectionHeuristic textDir) {
+        return MeasuredText.build(text, paint, textDir, 0, text.length());
     }
 
     /**
-     * Recycle the MeasuredText.
+     * Build MeasuredText from the specific range of the text..
      *
-     * Do not call any methods after you call this method.
+     * @param text The text to be measured.
+     * @param paint The paint to be used for drawing.
+     * @param textDir The text direction.
+     * @param start The inclusive start offset of the text.
+     * @param end The exclusive start offset of the text.
+     * @return The measured text.
      */
-    public void recycle() {
-        release();
-        sPool.release(this);
+    public static @NonNull MeasuredText build(@NonNull CharSequence text,
+                                              @NonNull TextPaint paint,
+                                              @NonNull TextDirectionHeuristic textDir,
+                                              @IntRange(from = 0) int start,
+                                              @IntRange(from = 0) int end) {
+        Preconditions.checkNotNull(text);
+        Preconditions.checkNotNull(paint);
+        Preconditions.checkNotNull(textDir);
+        Preconditions.checkArgumentInRange(start, 0, text.length(), "start");
+        Preconditions.checkArgumentInRange(end, 0, text.length(), "end");
+
+        final IntArray paragraphEnds = new IntArray();
+        final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>();
+
+        int paraEnd = 0;
+        for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
+            paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
+            if (paraEnd < 0) {
+                // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end.
+                paraEnd = end;
+            } else {
+                paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
+            }
+
+            paragraphEnds.add(paraEnd);
+            measuredTexts.add(MeasuredParagraph.buildForStaticLayout(
+                    paint, text, paraStart, paraEnd, textDir, null /* no recycle */));
+        }
+
+        return new MeasuredText(text, start, end, paint, textDir,
+                                measuredTexts.toArray(new MeasuredParagraph[measuredTexts.size()]),
+                                paragraphEnds.toArray());
     }
 
-    // The casted original text.
+    // Use MeasuredText.build instead.
+    private MeasuredText(@NonNull CharSequence text,
+                         @IntRange(from = 0) int start,
+                         @IntRange(from = 0) int end,
+                         @NonNull TextPaint paint,
+                         @NonNull TextDirectionHeuristic textDir,
+                         @NonNull MeasuredParagraph[] measuredTexts,
+                         @NonNull int[] paragraphBreakPoints) {
+        mText = text;
+        mStart = start;
+        mEnd = end;
+        mPaint = paint;
+        mMeasuredParagraphs = measuredTexts;
+        mParagraphBreakPoints = paragraphBreakPoints;
+        mTextDir = textDir;
+    }
+
+    /**
+     * Return the underlying text.
+     */
+    public @NonNull CharSequence getText() {
+        return mText;
+    }
+
+    /**
+     * Returns the inclusive start offset of measured region.
+     */
+    public @IntRange(from = 0) int getStart() {
+        return mStart;
+    }
+
+    /**
+     * Returns the exclusive end offset of measured region.
+     */
+    public @IntRange(from = 0) int getEnd() {
+        return mEnd;
+    }
+
+    /**
+     * Returns the text direction associated with char sequence.
+     */
+    public @NonNull TextDirectionHeuristic getTextDir() {
+        return mTextDir;
+    }
+
+    /**
+     * Returns the paint used to measure this text.
+     */
+    public @NonNull TextPaint getPaint() {
+        return mPaint;
+    }
+
+    /**
+     * Returns the length of the paragraph of this text.
+     */
+    public @IntRange(from = 0) int getParagraphCount() {
+        return mParagraphBreakPoints.length;
+    }
+
+    /**
+     * Returns the paragraph start offset of the text.
+     */
+    public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) {
+        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
+        return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1];
+    }
+
+    /**
+     * Returns the paragraph end offset of the text.
+     */
+    public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) {
+        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
+        return mParagraphBreakPoints[paraIndex];
+    }
+
+    /** @hide */
+    public @NonNull MeasuredParagraph getMeasuredParagraph(@IntRange(from = 0) int paraIndex) {
+        return mMeasuredParagraphs[paraIndex];
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Spanned overrides
     //
-    // This may be null if the passed text is not a Spanned.
-    private @Nullable Spanned mSpanned;
+    // Just proxy for underlying mText if appropriate.
 
-    // The start offset of the target range in the original text (mSpanned);
-    private @IntRange(from = 0) int mTextStart;
+    @Override
+    public <T> T[] getSpans(int start, int end, Class<T> type) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).getSpans(start, end, type);
+        } else {
+            return ArrayUtils.emptyArray(type);
+        }
+    }
 
-    // The length of the target range in the original text.
-    private @IntRange(from = 0) int mTextLength;
+    @Override
+    public int getSpanStart(Object tag) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).getSpanStart(tag);
+        } else {
+            return -1;
+        }
+    }
 
-    // The copied character buffer for measuring text.
+    @Override
+    public int getSpanEnd(Object tag) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).getSpanEnd(tag);
+        } else {
+            return -1;
+        }
+    }
+
+    @Override
+    public int getSpanFlags(Object tag) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).getSpanFlags(tag);
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public int nextSpanTransition(int start, int limit, Class type) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).nextSpanTransition(start, limit, type);
+        } else {
+            return mText.length();
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // CharSequence overrides.
     //
-    // The length of this array is mTextLength.
-    private @Nullable char[] mCopiedBuffer;
+    // Just proxy for underlying mText.
 
-    // The whole paragraph direction.
-    private @Layout.Direction int mParaDir;
-
-    // True if the text is LTR direction and doesn't contain any bidi characters.
-    private boolean mLtrWithoutBidi;
-
-    // The bidi level for individual characters.
-    //
-    // This is empty if mLtrWithoutBidi is true.
-    private @NonNull ByteArray mLevels = new ByteArray();
-
-    // The whole width of the text.
-    // See getWholeWidth comments.
-    private @FloatRange(from = 0.0f) float mWholeWidth;
-
-    // Individual characters' widths.
-    // See getWidths comments.
-    private @Nullable FloatArray mWidths = new FloatArray();
-
-    // The span end positions.
-    // See getSpanEndCache comments.
-    private @Nullable IntArray mSpanEndCache = new IntArray(4);
-
-    // The font metrics.
-    // See getFontMetrics comments.
-    private @Nullable IntArray mFontMetrics = new IntArray(4 * 4);
-
-    // The native MeasuredText.
-    // See getNativePtr comments.
-    // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead.
-    private /* Maybe Zero */ long mNativePtr = 0;
-    private @Nullable Runnable mNativeObjectCleaner;
-
-    // Associate the native object to this Java object.
-    private void bindNativeObject(/* Non Zero*/ long nativePtr) {
-        mNativePtr = nativePtr;
-        mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr);
+    @Override
+    public int length() {
+        return mText.length();
     }
 
-    // Decouple the native object from this Java object and release the native object.
-    private void unbindNativeObject() {
-        if (mNativePtr != 0) {
-            mNativeObjectCleaner.run();
-            mNativePtr = 0;
-        }
+    @Override
+    public char charAt(int index) {
+        // TODO: Should this be index + mStart ?
+        return mText.charAt(index);
     }
 
-    // Following two objects are for avoiding object allocation.
-    private @NonNull TextPaint mCachedPaint = new TextPaint();
-    private @Nullable Paint.FontMetricsInt mCachedFm;
-
-    /**
-     * Releases internal buffers.
-     */
-    public void release() {
-        reset();
-        mLevels.clearWithReleasingLargeArray();
-        mWidths.clearWithReleasingLargeArray();
-        mFontMetrics.clearWithReleasingLargeArray();
-        mSpanEndCache.clearWithReleasingLargeArray();
+    @Override
+    public CharSequence subSequence(int start, int end) {
+        // TODO: return MeasuredText.
+        // TODO: Should this be index + mStart, end + mStart ?
+        return mText.subSequence(start, end);
     }
 
-    /**
-     * Resets the internal state for starting new text.
-     */
-    private void reset() {
-        mSpanned = null;
-        mCopiedBuffer = null;
-        mWholeWidth = 0;
-        mLevels.clear();
-        mWidths.clear();
-        mFontMetrics.clear();
-        mSpanEndCache.clear();
-        unbindNativeObject();
+    @Override
+    public String toString() {
+        return mText.toString();
     }
-
-    /**
-     * Returns the characters to be measured.
-     *
-     * This is always available.
-     */
-    public @NonNull char[] getChars() {
-        return mCopiedBuffer;
-    }
-
-    /**
-     * Returns the paragraph direction.
-     *
-     * This is always available.
-     */
-    public @Layout.Direction int getParagraphDir() {
-        return mParaDir;
-    }
-
-    /**
-     * Returns the directions.
-     *
-     * This is always available.
-     */
-    public Directions getDirections(@IntRange(from = 0) int start,  // inclusive
-                                    @IntRange(from = 0) int end) {  // exclusive
-        if (mLtrWithoutBidi) {
-            return Layout.DIRS_ALL_LEFT_TO_RIGHT;
-        }
-
-        final int length = end - start;
-        return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start,
-                length);
-    }
-
-    /**
-     * Returns the whole text width.
-     *
-     * This is available only if the MeasureText is computed with computeForMeasurement.
-     * Returns 0 in other cases.
-     */
-    public @FloatRange(from = 0.0f) float getWholeWidth() {
-        return mWholeWidth;
-    }
-
-    /**
-     * Returns the individual character's width.
-     *
-     * This is available only if the MeasureText is computed with computeForMeasurement.
-     * Returns empty array in other cases.
-     */
-    public @NonNull FloatArray getWidths() {
-        return mWidths;
-    }
-
-    /**
-     * Returns the MetricsAffectingSpan end indices.
-     *
-     * If the input text is not a spanned string, this has one value that is the length of the text.
-     *
-     * This is available only if the MeasureText is computed with computeForStaticLayout.
-     * Returns empty array in other cases.
-     */
-    public @NonNull IntArray getSpanEndCache() {
-        return mSpanEndCache;
-    }
-
-    /**
-     * Returns the int array which holds FontMetrics.
-     *
-     * This array holds the repeat of top, bottom, ascent, descent of font metrics value.
-     *
-     * This is available only if the MeasureText is computed with computeForStaticLayout.
-     * Returns empty array in other cases.
-     */
-    public @NonNull IntArray getFontMetrics() {
-        return mFontMetrics;
-    }
-
-    /**
-     * Returns the native ptr of the MeasuredText.
-     *
-     * This is available only if the MeasureText is computed with computeForStaticLayout.
-     * Returns 0 in other cases.
-     */
-    public /* Maybe Zero */ long getNativePtr() {
-        return mNativePtr;
-    }
-
-    /**
-     * Generates new MeasuredText for Bidi computation.
-     *
-     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
-     * result to recycle and returns recycle.
-     *
-     * @param text the character sequence to be measured
-     * @param start the inclusive start offset of the target region in the text
-     * @param end the exclusive end offset of the target region in the text
-     * @param textDir the text direction
-     * @param recycle pass existing MeasuredText if you want to recycle it.
-     *
-     * @return measured text
-     */
-    public static @NonNull MeasuredText buildForBidi(@NonNull CharSequence text,
-                                                     @IntRange(from = 0) int start,
-                                                     @IntRange(from = 0) int end,
-                                                     @NonNull TextDirectionHeuristic textDir,
-                                                     @Nullable MeasuredText recycle) {
-        final MeasuredText mt = recycle == null ? obtain() : recycle;
-        mt.resetAndAnalyzeBidi(text, start, end, textDir);
-        return mt;
-    }
-
-    /**
-     * Generates new MeasuredText for measuring texts.
-     *
-     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
-     * result to recycle and returns recycle.
-     *
-     * @param paint the paint to be used for rendering the text.
-     * @param text the character sequence to be measured
-     * @param start the inclusive start offset of the target region in the text
-     * @param end the exclusive end offset of the target region in the text
-     * @param textDir the text direction
-     * @param recycle pass existing MeasuredText if you want to recycle it.
-     *
-     * @return measured text
-     */
-    public static @NonNull MeasuredText buildForMeasurement(@NonNull TextPaint paint,
-                                                            @NonNull CharSequence text,
-                                                            @IntRange(from = 0) int start,
-                                                            @IntRange(from = 0) int end,
-                                                            @NonNull TextDirectionHeuristic textDir,
-                                                            @Nullable MeasuredText recycle) {
-        final MeasuredText mt = recycle == null ? obtain() : recycle;
-        mt.resetAndAnalyzeBidi(text, start, end, textDir);
-
-        mt.mWidths.resize(mt.mTextLength);
-        if (mt.mTextLength == 0) {
-            return mt;
-        }
-
-        if (mt.mSpanned == null) {
-            // No style change by MetricsAffectingSpan. Just measure all text.
-            mt.applyMetricsAffectingSpan(
-                    paint, null /* spans */, start, end, 0 /* native static layout ptr */);
-        } else {
-            // There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
-            int spanEnd;
-            for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
-                spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class);
-                MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
-                        MetricAffectingSpan.class);
-                spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
-                mt.applyMetricsAffectingSpan(
-                        paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */);
-            }
-        }
-        return mt;
-    }
-
-    /**
-     * Generates new MeasuredText for StaticLayout.
-     *
-     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
-     * result to recycle and returns recycle.
-     *
-     * @param paint the paint to be used for rendering the text.
-     * @param text the character sequence to be measured
-     * @param start the inclusive start offset of the target region in the text
-     * @param end the exclusive end offset of the target region in the text
-     * @param textDir the text direction
-     * @param recycle pass existing MeasuredText if you want to recycle it.
-     *
-     * @return measured text
-     */
-    public static @NonNull MeasuredText buildForStaticLayout(
-            @NonNull TextPaint paint,
-            @NonNull CharSequence text,
-            @IntRange(from = 0) int start,
-            @IntRange(from = 0) int end,
-            @NonNull TextDirectionHeuristic textDir,
-            @Nullable MeasuredText recycle) {
-        final MeasuredText mt = recycle == null ? obtain() : recycle;
-        mt.resetAndAnalyzeBidi(text, start, end, textDir);
-        if (mt.mTextLength == 0) {
-            // Need to build empty native measured text for StaticLayout.
-            // TODO: Stop creating empty measured text for empty lines.
-            long nativeBuilderPtr = nInitBuilder();
-            try {
-                mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer));
-            } finally {
-                nFreeBuilder(nativeBuilderPtr);
-            }
-            return mt;
-        }
-
-        long nativeBuilderPtr = nInitBuilder();
-        try {
-            if (mt.mSpanned == null) {
-                // No style change by MetricsAffectingSpan. Just measure all text.
-                mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr);
-                mt.mSpanEndCache.append(end);
-            } else {
-                // There may be a MetricsAffectingSpan. Split into span transitions and apply
-                // styles.
-                int spanEnd;
-                for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
-                    spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
-                                                             MetricAffectingSpan.class);
-                    MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
-                            MetricAffectingSpan.class);
-                    spans = TextUtils.removeEmptySpans(spans, mt.mSpanned,
-                                                       MetricAffectingSpan.class);
-                    mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd,
-                                                 nativeBuilderPtr);
-                    mt.mSpanEndCache.append(spanEnd);
-                }
-            }
-            mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer));
-        } finally {
-            nFreeBuilder(nativeBuilderPtr);
-        }
-
-        return mt;
-    }
-
-    /**
-     * Reset internal state and analyzes text for bidirectional runs.
-     *
-     * @param text the character sequence to be measured
-     * @param start the inclusive start offset of the target region in the text
-     * @param end the exclusive end offset of the target region in the text
-     * @param textDir the text direction
-     */
-    private void resetAndAnalyzeBidi(@NonNull CharSequence text,
-                                     @IntRange(from = 0) int start,  // inclusive
-                                     @IntRange(from = 0) int end,  // exclusive
-                                     @NonNull TextDirectionHeuristic textDir) {
-        reset();
-        mSpanned = text instanceof Spanned ? (Spanned) text : null;
-        mTextStart = start;
-        mTextLength = end - start;
-
-        if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) {
-            mCopiedBuffer = new char[mTextLength];
-        }
-        TextUtils.getChars(text, start, end, mCopiedBuffer, 0);
-
-        // Replace characters associated with ReplacementSpan to U+FFFC.
-        if (mSpanned != null) {
-            ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class);
-
-            for (int i = 0; i < spans.length; i++) {
-                int startInPara = mSpanned.getSpanStart(spans[i]) - start;
-                int endInPara = mSpanned.getSpanEnd(spans[i]) - start;
-                // The span interval may be larger and must be restricted to [start, end)
-                if (startInPara < 0) startInPara = 0;
-                if (endInPara > mTextLength) endInPara = mTextLength;
-                Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER);
-            }
-        }
-
-        if ((textDir == TextDirectionHeuristics.LTR ||
-                textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR ||
-                textDir == TextDirectionHeuristics.ANYRTL_LTR) &&
-                TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
-            mLevels.clear();
-            mParaDir = Layout.DIR_LEFT_TO_RIGHT;
-            mLtrWithoutBidi = true;
-        } else {
-            final int bidiRequest;
-            if (textDir == TextDirectionHeuristics.LTR) {
-                bidiRequest = Layout.DIR_REQUEST_LTR;
-            } else if (textDir == TextDirectionHeuristics.RTL) {
-                bidiRequest = Layout.DIR_REQUEST_RTL;
-            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
-                bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
-            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
-                bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
-            } else {
-                final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
-                bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
-            }
-            mLevels.resize(mTextLength);
-            mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray());
-            mLtrWithoutBidi = false;
-        }
-    }
-
-    private void applyReplacementRun(@NonNull ReplacementSpan replacement,
-                                     @IntRange(from = 0) int start,  // inclusive, in copied buffer
-                                     @IntRange(from = 0) int end,  // exclusive, in copied buffer
-                                     /* Maybe Zero */ long nativeBuilderPtr) {
-        // Use original text. Shouldn't matter.
-        // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for
-        //       backward compatibility? or Should we initialize them for getFontMetricsInt?
-        final float width = replacement.getSize(
-                mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm);
-        if (nativeBuilderPtr == 0) {
-            // Assigns all width to the first character. This is the same behavior as minikin.
-            mWidths.set(start, width);
-            if (end > start + 1) {
-                Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f);
-            }
-            mWholeWidth += width;
-        } else {
-            nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
-                               width);
-        }
-    }
-
-    private void applyStyleRun(@IntRange(from = 0) int start,  // inclusive, in copied buffer
-                               @IntRange(from = 0) int end,  // exclusive, in copied buffer
-                               /* Maybe Zero */ long nativeBuilderPtr) {
-        if (nativeBuilderPtr != 0) {
-            mCachedPaint.getFontMetricsInt(mCachedFm);
-        }
-
-        if (mLtrWithoutBidi) {
-            // If the whole text is LTR direction, just apply whole region.
-            if (nativeBuilderPtr == 0) {
-                mWholeWidth += mCachedPaint.getTextRunAdvances(
-                        mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
-                        mWidths.getRawArray(), start);
-            } else {
-                nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
-                        false /* isRtl */);
-            }
-        } else {
-            // If there is multiple bidi levels, split into individual bidi level and apply style.
-            byte level = mLevels.get(start);
-            // Note that the empty text or empty range won't reach this method.
-            // Safe to search from start + 1.
-            for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) {
-                if (levelEnd == end || mLevels.get(levelEnd) != level) {  // transition point
-                    final boolean isRtl = (level & 0x1) != 0;
-                    if (nativeBuilderPtr == 0) {
-                        final int levelLength = levelEnd - levelStart;
-                        mWholeWidth += mCachedPaint.getTextRunAdvances(
-                                mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
-                                isRtl, mWidths.getRawArray(), levelStart);
-                    } else {
-                        nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart,
-                                levelEnd, isRtl);
-                    }
-                    if (levelEnd == end) {
-                        break;
-                    }
-                    levelStart = levelEnd;
-                    level = mLevels.get(levelEnd);
-                }
-            }
-        }
-    }
-
-    private void applyMetricsAffectingSpan(
-            @NonNull TextPaint paint,
-            @Nullable MetricAffectingSpan[] spans,
-            @IntRange(from = 0) int start,  // inclusive, in original text buffer
-            @IntRange(from = 0) int end,  // exclusive, in original text buffer
-            /* Maybe Zero */ long nativeBuilderPtr) {
-        mCachedPaint.set(paint);
-        // XXX paint should not have a baseline shift, but...
-        mCachedPaint.baselineShift = 0;
-
-        final boolean needFontMetrics = nativeBuilderPtr != 0;
-
-        if (needFontMetrics && mCachedFm == null) {
-            mCachedFm = new Paint.FontMetricsInt();
-        }
-
-        ReplacementSpan replacement = null;
-        if (spans != null) {
-            for (int i = 0; i < spans.length; i++) {
-                MetricAffectingSpan span = spans[i];
-                if (span instanceof ReplacementSpan) {
-                    // The last ReplacementSpan is effective for backward compatibility reasons.
-                    replacement = (ReplacementSpan) span;
-                } else {
-                    // TODO: No need to call updateMeasureState for ReplacementSpan as well?
-                    span.updateMeasureState(mCachedPaint);
-                }
-            }
-        }
-
-        final int startInCopiedBuffer = start - mTextStart;
-        final int endInCopiedBuffer = end - mTextStart;
-
-        if (replacement != null) {
-            applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer,
-                                nativeBuilderPtr);
-        } else {
-            applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr);
-        }
-
-        if (needFontMetrics) {
-            if (mCachedPaint.baselineShift < 0) {
-                mCachedFm.ascent += mCachedPaint.baselineShift;
-                mCachedFm.top += mCachedPaint.baselineShift;
-            } else {
-                mCachedFm.descent += mCachedPaint.baselineShift;
-                mCachedFm.bottom += mCachedPaint.baselineShift;
-            }
-
-            mFontMetrics.append(mCachedFm.top);
-            mFontMetrics.append(mCachedFm.bottom);
-            mFontMetrics.append(mCachedFm.ascent);
-            mFontMetrics.append(mCachedFm.descent);
-        }
-    }
-
-    /**
-     * Returns the maximum index that the accumulated width not exceeds the width.
-     *
-     * If forward=false is passed, returns the minimum index from the end instead.
-     *
-     * This only works if the MeasuredText is computed with computeForMeasurement.
-     * Undefined behavior in other case.
-     */
-    @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) {
-        float[] w = mWidths.getRawArray();
-        if (forwards) {
-            int i = 0;
-            while (i < limit) {
-                width -= w[i];
-                if (width < 0.0f) break;
-                i++;
-            }
-            while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--;
-            return i;
-        } else {
-            int i = limit - 1;
-            while (i >= 0) {
-                width -= w[i];
-                if (width < 0.0f) break;
-                i--;
-            }
-            while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) {
-                i++;
-            }
-            return limit - i - 1;
-        }
-    }
-
-    /**
-     * Returns the length of the substring.
-     *
-     * This only works if the MeasuredText is computed with computeForMeasurement.
-     * Undefined behavior in other case.
-     */
-    @FloatRange(from = 0.0f) float measure(int start, int limit) {
-        float width = 0;
-        float[] w = mWidths.getRawArray();
-        for (int i = start; i < limit; ++i) {
-            width += w[i];
-        }
-        return width;
-    }
-
-    private static native /* Non Zero */ long nInitBuilder();
-
-    /**
-     * Apply style to make native measured text.
-     *
-     * @param nativeBuilderPtr The native MeasuredText builder pointer.
-     * @param paintPtr The native paint pointer to be applied.
-     * @param start The start offset in the copied buffer.
-     * @param end The end offset in the copied buffer.
-     * @param isRtl True if the text is RTL.
-     */
-    private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
-                                            /* Non Zero */ long paintPtr,
-                                            @IntRange(from = 0) int start,
-                                            @IntRange(from = 0) int end,
-                                            boolean isRtl);
-
-    /**
-     * Apply ReplacementRun to make native measured text.
-     *
-     * @param nativeBuilderPtr The native MeasuredText builder pointer.
-     * @param paintPtr The native paint pointer to be applied.
-     * @param start The start offset in the copied buffer.
-     * @param end The end offset in the copied buffer.
-     * @param width The width of the replacement.
-     */
-    private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr,
-                                                  /* Non Zero */ long paintPtr,
-                                                  @IntRange(from = 0) int start,
-                                                  @IntRange(from = 0) int end,
-                                                  @FloatRange(from = 0) float width);
-
-    private static native long nBuildNativeMeasuredText(/* Non Zero */ long nativeBuilderPtr,
-                                                 @NonNull char[] text);
-
-    private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
-
-    @CriticalNative
-    private static native /* Non Zero */ long nGetReleaseFunc();
 }
diff --git a/core/java/android/text/PremeasuredText.java b/core/java/android/text/PremeasuredText.java
deleted file mode 100644
index 465314d..0000000
--- a/core/java/android/text/PremeasuredText.java
+++ /dev/null
@@ -1,272 +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 android.text;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.util.IntArray;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-
-/**
- * A text which has already been measured.
- *
- * TODO: Rename to better name? e.g. MeasuredText, FrozenText etc.
- */
-public class PremeasuredText implements Spanned {
-    private static final char LINE_FEED = '\n';
-
-    // The original text.
-    private final @NonNull CharSequence mText;
-
-    // The inclusive start offset of the measuring target.
-    private final @IntRange(from = 0) int mStart;
-
-    // The exclusive end offset of the measuring target.
-    private final @IntRange(from = 0) int mEnd;
-
-    // The TextPaint used for measurement.
-    private final @NonNull TextPaint mPaint;
-
-    // The requested text direction.
-    private final @NonNull TextDirectionHeuristic mTextDir;
-
-    // The measured paragraph texts.
-    private final @NonNull MeasuredText[] mMeasuredTexts;
-
-    // The sorted paragraph end offsets.
-    private final @NonNull int[] mParagraphBreakPoints;
-
-    /**
-     * Build PremeasuredText from the text.
-     *
-     * @param text The text to be measured.
-     * @param paint The paint to be used for drawing.
-     * @param textDir The text direction.
-     * @return The measured text.
-     */
-    public static @NonNull PremeasuredText build(@NonNull CharSequence text,
-                                                 @NonNull TextPaint paint,
-                                                 @NonNull TextDirectionHeuristic textDir) {
-        return PremeasuredText.build(text, paint, textDir, 0, text.length());
-    }
-
-    /**
-     * Build PremeasuredText from the specific range of the text..
-     *
-     * @param text The text to be measured.
-     * @param paint The paint to be used for drawing.
-     * @param textDir The text direction.
-     * @param start The inclusive start offset of the text.
-     * @param end The exclusive start offset of the text.
-     * @return The measured text.
-     */
-    public static @NonNull PremeasuredText build(@NonNull CharSequence text,
-                                                 @NonNull TextPaint paint,
-                                                 @NonNull TextDirectionHeuristic textDir,
-                                                 @IntRange(from = 0) int start,
-                                                 @IntRange(from = 0) int end) {
-        Preconditions.checkNotNull(text);
-        Preconditions.checkNotNull(paint);
-        Preconditions.checkNotNull(textDir);
-        Preconditions.checkArgumentInRange(start, 0, text.length(), "start");
-        Preconditions.checkArgumentInRange(end, 0, text.length(), "end");
-
-        final IntArray paragraphEnds = new IntArray();
-        final ArrayList<MeasuredText> measuredTexts = new ArrayList<>();
-
-        int paraEnd = 0;
-        for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
-            paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
-            if (paraEnd < 0) {
-                // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end.
-                paraEnd = end;
-            } else {
-                paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
-            }
-
-            paragraphEnds.add(paraEnd);
-            measuredTexts.add(MeasuredText.buildForStaticLayout(
-                    paint, text, paraStart, paraEnd, textDir, null /* no recycle */));
-        }
-
-        return new PremeasuredText(text, start, end, paint, textDir,
-                                   measuredTexts.toArray(new MeasuredText[measuredTexts.size()]),
-                                   paragraphEnds.toArray());
-    }
-
-    // Use PremeasuredText.build instead.
-    private PremeasuredText(@NonNull CharSequence text,
-                            @IntRange(from = 0) int start,
-                            @IntRange(from = 0) int end,
-                            @NonNull TextPaint paint,
-                            @NonNull TextDirectionHeuristic textDir,
-                            @NonNull MeasuredText[] measuredTexts,
-                            @NonNull int[] paragraphBreakPoints) {
-        mText = text;
-        mStart = start;
-        mEnd = end;
-        mPaint = paint;
-        mMeasuredTexts = measuredTexts;
-        mParagraphBreakPoints = paragraphBreakPoints;
-        mTextDir = textDir;
-    }
-
-    /**
-     * Return the underlying text.
-     */
-    public @NonNull CharSequence getText() {
-        return mText;
-    }
-
-    /**
-     * Returns the inclusive start offset of measured region.
-     */
-    public @IntRange(from = 0) int getStart() {
-        return mStart;
-    }
-
-    /**
-     * Returns the exclusive end offset of measured region.
-     */
-    public @IntRange(from = 0) int getEnd() {
-        return mEnd;
-    }
-
-    /**
-     * Returns the text direction associated with char sequence.
-     */
-    public @NonNull TextDirectionHeuristic getTextDir() {
-        return mTextDir;
-    }
-
-    /**
-     * Returns the paint used to measure this text.
-     */
-    public @NonNull TextPaint getPaint() {
-        return mPaint;
-    }
-
-    /**
-     * Returns the length of the paragraph of this text.
-     */
-    public @IntRange(from = 0) int getParagraphCount() {
-        return mParagraphBreakPoints.length;
-    }
-
-    /**
-     * Returns the paragraph start offset of the text.
-     */
-    public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) {
-        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
-        return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1];
-    }
-
-    /**
-     * Returns the paragraph end offset of the text.
-     */
-    public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) {
-        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
-        return mParagraphBreakPoints[paraIndex];
-    }
-
-    /** @hide */
-    public @NonNull MeasuredText getMeasuredText(@IntRange(from = 0) int paraIndex) {
-        return mMeasuredTexts[paraIndex];
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    // Spanned overrides
-    //
-    // Just proxy for underlying mText if appropriate.
-
-    @Override
-    public <T> T[] getSpans(int start, int end, Class<T> type) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).getSpans(start, end, type);
-        } else {
-            return ArrayUtils.emptyArray(type);
-        }
-    }
-
-    @Override
-    public int getSpanStart(Object tag) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).getSpanStart(tag);
-        } else {
-            return -1;
-        }
-    }
-
-    @Override
-    public int getSpanEnd(Object tag) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).getSpanEnd(tag);
-        } else {
-            return -1;
-        }
-    }
-
-    @Override
-    public int getSpanFlags(Object tag) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).getSpanFlags(tag);
-        } else {
-            return 0;
-        }
-    }
-
-    @Override
-    public int nextSpanTransition(int start, int limit, Class type) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).nextSpanTransition(start, limit, type);
-        } else {
-            return mText.length();
-        }
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    // CharSequence overrides.
-    //
-    // Just proxy for underlying mText.
-
-    @Override
-    public int length() {
-        return mText.length();
-    }
-
-    @Override
-    public char charAt(int index) {
-        // TODO: Should this be index + mStart ?
-        return mText.charAt(index);
-    }
-
-    @Override
-    public CharSequence subSequence(int start, int end) {
-        // TODO: return PremeasuredText.
-        // TODO: Should this be index + mStart, end + mStart ?
-        return mText.subSequence(start, end);
-    }
-
-    @Override
-    public String toString() {
-        return mText.toString();
-    }
-}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index d69b119..36bec86 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -55,7 +55,8 @@
      * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
      * following:
      *
-     *   - Create MeasuredText by MeasuredText.buildForStaticLayout which measures in native.
+     *   - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
+     *     native.
      *   - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
      *
      * After all paragraphs, call finish() to release expensive buffers.
@@ -650,34 +651,34 @@
                 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
                 indents, mLeftPaddings, mRightPaddings);
 
-        PremeasuredText premeasured = null;
+        MeasuredText measured = null;
         final Spanned spanned;
-        if (source instanceof PremeasuredText) {
-            premeasured = (PremeasuredText) source;
+        if (source instanceof MeasuredText) {
+            measured = (MeasuredText) source;
 
-            final CharSequence original = premeasured.getText();
+            final CharSequence original = measured.getText();
             spanned = (original instanceof Spanned) ? (Spanned) original : null;
 
-            if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) {
+            if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) {
                 // The buffer position has changed. Re-measure here.
-                premeasured = PremeasuredText.build(original, paint, textDir, bufStart, bufEnd);
+                measured = MeasuredText.build(original, paint, textDir, bufStart, bufEnd);
             } else {
-                // We can use premeasured information.
+                // We can use measured information.
 
-                // Overwrite with the one when premeasured.
+                // Overwrite with the one when emeasured.
                 // TODO: Give an option for developer not to overwrite and measure again here?
-                textDir = premeasured.getTextDir();
-                paint = premeasured.getPaint();
+                textDir = measured.getTextDir();
+                paint = measured.getPaint();
             }
         } else {
-            premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd);
+            measured = MeasuredText.build(source, paint, textDir, bufStart, bufEnd);
             spanned = (source instanceof Spanned) ? (Spanned) source : null;
         }
 
         try {
-            for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) {
-                final int paraStart = premeasured.getParagraphStart(paraIndex);
-                final int paraEnd = premeasured.getParagraphEnd(paraIndex);
+            for (int paraIndex = 0; paraIndex < measured.getParagraphCount(); paraIndex++) {
+                final int paraStart = measured.getParagraphStart(paraIndex);
+                final int paraEnd = measured.getParagraphEnd(paraIndex);
 
                 int firstWidthLineCount = 1;
                 int firstWidth = outerWidth;
@@ -743,10 +744,10 @@
                     }
                 }
 
-                final MeasuredText measured = premeasured.getMeasuredText(paraIndex);
-                final char[] chs = measured.getChars();
-                final int[] spanEndCache = measured.getSpanEndCache().getRawArray();
-                final int[] fmCache = measured.getFontMetrics().getRawArray();
+                final MeasuredParagraph measuredPara = measured.getMeasuredParagraph(paraIndex);
+                final char[] chs = measuredPara.getChars();
+                final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
+                final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
                 // TODO: Stop keeping duplicated width copy in native and Java.
                 widths.resize(chs.length);
 
@@ -759,7 +760,7 @@
 
                         // Inputs
                         chs,
-                        measured.getNativePtr(),
+                        measuredPara.getNativePtr(),
                         paraEnd - paraStart,
                         firstWidth,
                         firstWidthLineCount,
@@ -863,7 +864,7 @@
                         v = out(source, here, endPos,
                                 ascent, descent, fmTop, fmBottom,
                                 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
-                                flags[breakIndex], needMultiply, measured, bufEnd,
+                                flags[breakIndex], needMultiply, measuredPara, bufEnd,
                                 includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
                                 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
                                 paint, moreChars);
@@ -894,8 +895,8 @@
 
             if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
                     && mLineCount < mMaximumVisibleLineCount) {
-                final MeasuredText measured =
-                        MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null);
+                final MeasuredParagraph measuredPara =
+                        MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
                 paint.getFontMetricsInt(fm);
                 v = out(source,
                         bufEnd, bufEnd, fm.ascent, fm.descent,
@@ -903,7 +904,7 @@
                         v,
                         spacingmult, spacingadd, null,
                         null, fm, 0,
-                        needMultiply, measured, bufEnd,
+                        needMultiply, measuredPara, bufEnd,
                         includepad, trackpad, addLastLineSpacing, null,
                         null, bufStart, ellipsize,
                         ellipsizedWidth, 0, paint, false);
@@ -918,7 +919,7 @@
     private int out(final CharSequence text, final int start, final int end, int above, int below,
             int top, int bottom, int v, final float spacingmult, final float spacingadd,
             final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
-            final int flags, final boolean needMultiply, @NonNull final MeasuredText measured,
+            final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
             final int bufEnd, final boolean includePad, final boolean trackPad,
             final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
             final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 9c9fbf2..409e514 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1250,10 +1250,10 @@
             @NonNull String ellipsis) {
 
         final int len = text.length();
-        MeasuredText mt = null;
-        MeasuredText resultMt = null;
+        MeasuredParagraph mt = null;
+        MeasuredParagraph resultMt = null;
         try {
-            mt = MeasuredText.buildForMeasurement(paint, text, 0, text.length(), textDir, mt);
+            mt = MeasuredParagraph.buildForMeasurement(paint, text, 0, text.length(), textDir, mt);
             float width = mt.getWholeWidth();
 
             if (width <= avail) {
@@ -1332,7 +1332,7 @@
                 if (remaining == 0) { // All text is gone.
                     textFits = true;
                 } else {
-                    resultMt = MeasuredText.buildForMeasurement(
+                    resultMt = MeasuredParagraph.buildForMeasurement(
                             paint, result, 0, result.length(), textDir, resultMt);
                     width = resultMt.getWholeWidth();
                     if (width <= avail) {
@@ -1479,11 +1479,11 @@
     public static CharSequence commaEllipsize(CharSequence text, TextPaint p,
          float avail, String oneMore, String more, TextDirectionHeuristic textDir) {
 
-        MeasuredText mt = null;
-        MeasuredText tempMt = null;
+        MeasuredParagraph mt = null;
+        MeasuredParagraph tempMt = null;
         try {
             int len = text.length();
-            mt = MeasuredText.buildForMeasurement(p, text, 0, len, textDir, mt);
+            mt = MeasuredParagraph.buildForMeasurement(p, text, 0, len, textDir, mt);
             final float width = mt.getWholeWidth();
             if (width <= avail) {
                 return text;
@@ -1523,7 +1523,7 @@
                     }
 
                     // XXX this is probably ok, but need to look at it more
-                    tempMt = MeasuredText.buildForMeasurement(
+                    tempMt = MeasuredParagraph.buildForMeasurement(
                             p, format, 0, format.length(), textDir, tempMt);
                     float moreWid = tempMt.getWholeWidth();
 
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 1d5392e..62f9717 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -39,11 +39,12 @@
         DEFAULT_FLAGS = new HashMap<>();
         DEFAULT_FLAGS.put("device_info_v2", "true");
         DEFAULT_FLAGS.put("settings_search_v2", "true");
-        DEFAULT_FLAGS.put("settings_app_info_v2", "false");
+        DEFAULT_FLAGS.put("settings_app_info_v2", "true");
         DEFAULT_FLAGS.put("settings_connected_device_v2", "true");
         DEFAULT_FLAGS.put("settings_battery_v2", "false");
         DEFAULT_FLAGS.put("settings_battery_display_app_list", "false");
         DEFAULT_FLAGS.put("settings_security_settings_v2", "true");
+        DEFAULT_FLAGS.put("settings_zone_picker_v2", "false");
     }
 
     /**
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index cc4a0b6..84ae20b 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -18,30 +18,18 @@
 
 import android.os.SystemClock;
 
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
 import libcore.util.TimeZoneFinder;
 import libcore.util.ZoneInfoDB;
 
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
 /**
  * A class containing utility methods related to time zones.
  */
 public class TimeUtils {
     /** @hide */ public TimeUtils() {}
-    private static final boolean DBG = false;
-    private static final String TAG = "TimeUtils";
-
-    /** Cached results of getTimeZonesWithUniqueOffsets */
-    private static final Object sLastUniqueLockObj = new Object();
-    private static List<String> sLastUniqueZoneOffsets = null;
-    private static String sLastUniqueCountry = null;
-
     /** {@hide} */
     private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
@@ -76,86 +64,6 @@
     }
 
     /**
-     * Returns an immutable list of unique time zone IDs for the country.
-     *
-     * @param country to find
-     * @return unmodifiable list of unique time zones, maybe empty but never null.
-     * @hide
-     */
-    public static List<String> getTimeZoneIdsWithUniqueOffsets(String country) {
-        synchronized(sLastUniqueLockObj) {
-            if ((country != null) && country.equals(sLastUniqueCountry)) {
-                if (DBG) {
-                    Log.d(TAG, "getTimeZonesWithUniqueOffsets(" +
-                            country + "): return cached version");
-                }
-                return sLastUniqueZoneOffsets;
-            }
-        }
-
-        Collection<android.icu.util.TimeZone> zones = getIcuTimeZones(country);
-        ArrayList<android.icu.util.TimeZone> uniqueTimeZones = new ArrayList<>();
-        for (android.icu.util.TimeZone zone : zones) {
-            // See if we already have this offset,
-            // Using slow but space efficient and these are small.
-            boolean found = false;
-            for (int i = 0; i < uniqueTimeZones.size(); i++) {
-                if (uniqueTimeZones.get(i).getRawOffset() == zone.getRawOffset()) {
-                    found = true;
-                    break;
-                }
-            }
-            if (!found) {
-                if (DBG) {
-                    Log.d(TAG, "getTimeZonesWithUniqueOffsets: add unique offset=" +
-                            zone.getRawOffset() + " zone.getID=" + zone.getID());
-                }
-                uniqueTimeZones.add(zone);
-            }
-        }
-
-        synchronized(sLastUniqueLockObj) {
-            // Cache the last result
-            sLastUniqueZoneOffsets = extractZoneIds(uniqueTimeZones);
-            sLastUniqueCountry = country;
-
-            return sLastUniqueZoneOffsets;
-        }
-    }
-
-    private static List<String> extractZoneIds(List<android.icu.util.TimeZone> timeZones) {
-        List<String> ids = new ArrayList<>(timeZones.size());
-        for (android.icu.util.TimeZone timeZone : timeZones) {
-            ids.add(timeZone.getID());
-        }
-        return Collections.unmodifiableList(ids);
-    }
-
-    /**
-     * Returns an immutable list of frozen ICU time zones for the country.
-     *
-     * @param countryIso is a two character country code.
-     * @return TimeZone list, maybe empty but never null.
-     * @hide
-     */
-    private static List<android.icu.util.TimeZone> getIcuTimeZones(String countryIso) {
-        if (countryIso == null) {
-            if (DBG) Log.d(TAG, "getIcuTimeZones(null): return empty list");
-            return Collections.emptyList();
-        }
-        List<android.icu.util.TimeZone> timeZones =
-                TimeZoneFinder.getInstance().lookupTimeZonesByCountry(countryIso);
-        if (timeZones == null) {
-            if (DBG) {
-                Log.d(TAG, "getIcuTimeZones(" + countryIso
-                        + "): returned null, converting to empty list");
-            }
-            return Collections.emptyList();
-        }
-        return timeZones;
-    }
-
-    /**
      * Returns a String indicating the version of the time zone database currently
      * in use.  The format of the string is dependent on the underlying time zone
      * database implementation, but will typically contain the year in which the database
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 8146729..555c474 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -25,6 +25,7 @@
 
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.Signature;
 import android.os.Trace;
 import android.util.jar.StrictJarFile;
@@ -52,10 +53,6 @@
  */
 public class ApkSignatureVerifier {
 
-    public static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
-    public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
-    public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
-
     private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
 
     /**
@@ -63,10 +60,11 @@
      *
      * @throws PackageParserException if the APK's signature failed to verify.
      */
-    public static Result verify(String apkPath, int minSignatureSchemeVersion)
+    public static PackageParser.SigningDetails verify(String apkPath,
+            @SignatureSchemeVersion int minSignatureSchemeVersion)
             throws PackageParserException {
 
-        if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) {
+        if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
             // V3 and before are older than the requested minimum signing version
             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                     "No signature found in package of version " + minSignatureSchemeVersion
@@ -80,10 +78,11 @@
                     ApkSignatureSchemeV3Verifier.verify(apkPath);
             Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
             Signature[] signerSigs = convertToSignatures(signerCerts);
-            return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3);
+            return new PackageParser.SigningDetails(signerSigs,
+                    SignatureSchemeVersion.SIGNING_BLOCK_V3);
         } catch (SignatureNotFoundException e) {
             // not signed with v2, try older if allowed
-            if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) {
+            if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                         "No APK Signature Scheme v3 signature in package " + apkPath, e);
             }
@@ -97,7 +96,7 @@
         }
 
         // redundant, protective version check
-        if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) {
+        if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) {
             // V2 and before are older than the requested minimum signing version
             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                     "No signature found in package of version " + minSignatureSchemeVersion
@@ -110,10 +109,11 @@
             Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
             Signature[] signerSigs = convertToSignatures(signerCerts);
 
-            return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2);
+            return new PackageParser.SigningDetails(
+                    signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V2);
         } catch (SignatureNotFoundException e) {
             // not signed with v2, try older if allowed
-            if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) {
+            if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                         "No APK Signature Scheme v2 signature in package " + apkPath, e);
             }
@@ -127,7 +127,7 @@
         }
 
         // redundant, protective version check
-        if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) {
+        if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) {
             // V1 and is older than the requested minimum signing version
             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                     "No signature found in package of version " + minSignatureSchemeVersion
@@ -145,7 +145,8 @@
      *
      * @throws PackageParserException if there was a problem collecting certificates
      */
-    private static Result verifyV1Signature(String apkPath, boolean verifyFull)
+    private static PackageParser.SigningDetails verifyV1Signature(
+            String apkPath, boolean verifyFull)
             throws PackageParserException {
         StrictJarFile jarFile = null;
 
@@ -211,7 +212,7 @@
                     }
                 }
             }
-            return new Result(lastCerts, lastSigs, VERSION_JAR_SIGNATURE_SCHEME);
+            return new PackageParser.SigningDetails(lastSigs, SignatureSchemeVersion.JAR);
         } catch (GeneralSecurityException e) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
                     "Failed to collect certificates from " + apkPath, e);
@@ -289,10 +290,11 @@
      * @throws PackageParserException if the APK's signature failed to verify.
      * or greater is not found, except in the case of no JAR signature.
      */
-    public static Result plsCertsNoVerifyOnlyCerts(String apkPath, int minSignatureSchemeVersion)
+    public static PackageParser.SigningDetails plsCertsNoVerifyOnlyCerts(
+            String apkPath, int minSignatureSchemeVersion)
             throws PackageParserException {
 
-        if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) {
+        if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
             // V3 and before are older than the requested minimum signing version
             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                     "No signature found in package of version " + minSignatureSchemeVersion
@@ -306,10 +308,11 @@
                     ApkSignatureSchemeV3Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
             Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
             Signature[] signerSigs = convertToSignatures(signerCerts);
-            return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3);
+            return new PackageParser.SigningDetails(signerSigs,
+                    SignatureSchemeVersion.SIGNING_BLOCK_V3);
         } catch (SignatureNotFoundException e) {
             // not signed with v2, try older if allowed
-            if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) {
+            if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                         "No APK Signature Scheme v3 signature in package " + apkPath, e);
             }
@@ -323,7 +326,7 @@
         }
 
         // redundant, protective version check
-        if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) {
+        if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) {
             // V2 and before are older than the requested minimum signing version
             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                     "No signature found in package of version " + minSignatureSchemeVersion
@@ -336,10 +339,11 @@
             Certificate[][] signerCerts =
                     ApkSignatureSchemeV2Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
             Signature[] signerSigs = convertToSignatures(signerCerts);
-            return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2);
+            return new PackageParser.SigningDetails(signerSigs,
+                    SignatureSchemeVersion.SIGNING_BLOCK_V2);
         } catch (SignatureNotFoundException e) {
             // not signed with v2, try older if allowed
-            if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) {
+            if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                         "No APK Signature Scheme v2 signature in package " + apkPath, e);
             }
@@ -353,7 +357,7 @@
         }
 
         // redundant, protective version check
-        if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) {
+        if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) {
             // V1 and is older than the requested minimum signing version
             throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                     "No signature found in package of version " + minSignatureSchemeVersion
@@ -363,19 +367,4 @@
         // v2 didn't work, try jarsigner
         return verifyV1Signature(apkPath, false);
     }
-
-    /**
-     * Result of a successful APK verification operation.
-     */
-    public static class Result {
-        public final Certificate[][] certs;
-        public final Signature[] sigs;
-        public final int signatureSchemeVersion;
-
-        public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) {
-            this.certs = certs;
-            this.sigs = sigs;
-            this.signatureSchemeVersion = signingVersion;
-        }
-    }
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7001067..8c70322 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -147,7 +147,7 @@
     void exitKeyguardSecurely(IOnKeyguardExitResult callback);
     boolean isKeyguardLocked();
     boolean isKeyguardSecure();
-    void dismissKeyguard(IKeyguardDismissCallback callback);
+    void dismissKeyguard(IKeyguardDismissCallback callback, CharSequence message);
 
     // Requires INTERACT_ACROSS_USERS_FULL permission
     void setSwitchingUser(boolean switching);
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index a2147b7..ed2b8b6 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -804,8 +804,7 @@
     public static final int KEYCODE_SYSTEM_NAVIGATION_LEFT = 282;
     /** Key code constant: Consumed by the system for navigation right */
     public static final int KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283;
-    /** Key code constant: Show all apps
-     * @hide */
+    /** Key code constant: Show all apps */
     public static final int KEYCODE_ALL_APPS = 284;
 
     private static final int LAST_KEYCODE = KEYCODE_ALL_APPS;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f62189e..ad71b58 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4045,6 +4045,12 @@
     private CharSequence mContentDescription;
 
     /**
+     * If this view represents a distinct part of the window, it can have a title that labels the
+     * area.
+     */
+    private CharSequence mAccessibilityPaneTitle;
+
+    /**
      * Specifies the id of a view for which this view serves as a label for
      * accessibility purposes.
      */
@@ -4412,7 +4418,6 @@
     private CheckForLongPress mPendingCheckForLongPress;
     private CheckForTap mPendingCheckForTap = null;
     private PerformClick mPerformClick;
-    private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent;
 
     private UnsetPressedState mUnsetPressedState;
 
@@ -5409,6 +5414,11 @@
                         setScreenReaderFocusable(a.getBoolean(attr, false));
                     }
                     break;
+                case R.styleable.View_accessibilityPaneTitle:
+                    if (a.peekValue(attr) != null) {
+                        setAccessibilityPaneTitle(a.getString(attr));
+                    }
+                    break;
             }
         }
 
@@ -7164,7 +7174,7 @@
         if (gainFocus) {
             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
         } else {
-            notifyViewAccessibilityStateChangedIfNeeded(
+            notifyAccessibilityStateChanged(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
         }
 
@@ -7218,6 +7228,34 @@
     }
 
     /**
+     * If this view is a visually distinct portion of a window, for example the content view of
+     * a fragment that is replaced, it is considered a pane for accessibility purposes. In order
+     * for accessibility services to understand the views role, and to announce its title as
+     * appropriate, such views should have pane titles.
+     *
+     * @param accessibilityPaneTitle The pane's title.
+     *
+     * {@see AccessibilityNodeInfo#setPaneTitle(CharSequence)}
+     */
+    public void setAccessibilityPaneTitle(CharSequence accessibilityPaneTitle) {
+        if (!TextUtils.equals(accessibilityPaneTitle, mAccessibilityPaneTitle)) {
+            mAccessibilityPaneTitle = accessibilityPaneTitle;
+            notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE);
+        }
+    }
+
+    /**
+     * Get the title of the pane for purposes of accessibility.
+     *
+     * @return The current pane title.
+     *
+     * {@see #setAccessibilityPaneTitle}.
+     */
+    public CharSequence getAccessibilityPaneTitle() {
+        return mAccessibilityPaneTitle;
+    }
+
+    /**
      * Sends an accessibility event of the given type. If accessibility is
      * not enabled this method has no effect. The default implementation calls
      * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)} first
@@ -8514,6 +8552,7 @@
 
         info.addAction(AccessibilityAction.ACTION_SHOW_ON_SCREEN);
         populateAccessibilityNodeInfoDrawingOrderInParent(info);
+        info.setPaneTitle(mAccessibilityPaneTitle);
     }
 
     /**
@@ -8834,9 +8873,9 @@
         final boolean nonEmptyDesc = contentDescription != null && contentDescription.length() > 0;
         if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-            notifySubtreeAccessibilityStateChangedIfNeeded();
+            notifyAccessibilitySubtreeChanged();
         } else {
-            notifyViewAccessibilityStateChangedIfNeeded(
+            notifyAccessibilityStateChanged(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION);
         }
     }
@@ -8869,7 +8908,7 @@
             return;
         }
         mAccessibilityTraversalBeforeId = beforeId;
-        notifyViewAccessibilityStateChangedIfNeeded(
+        notifyAccessibilityStateChanged(
                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
@@ -8913,7 +8952,7 @@
             return;
         }
         mAccessibilityTraversalAfterId = afterId;
-        notifyViewAccessibilityStateChangedIfNeeded(
+        notifyAccessibilityStateChanged(
                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
@@ -8956,7 +8995,7 @@
                 && mID == View.NO_ID) {
             mID = generateViewId();
         }
-        notifyViewAccessibilityStateChangedIfNeeded(
+        notifyAccessibilityStateChanged(
                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
@@ -10457,8 +10496,7 @@
 
         if (pflags3 != mPrivateFlags3) {
             mPrivateFlags3 = pflags3;
-            notifyViewAccessibilityStateChangedIfNeeded(
-                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+            notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
         }
     }
 
@@ -11286,7 +11324,7 @@
             mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
             mPrivateFlags2 |= (mode << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT)
                     & PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
-            notifyViewAccessibilityStateChangedIfNeeded(
+            notifyAccessibilityStateChanged(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
         }
     }
@@ -11344,9 +11382,9 @@
             mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT)
                     & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK;
             if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) {
-                notifySubtreeAccessibilityStateChangedIfNeeded();
+                notifyAccessibilitySubtreeChanged();
             } else {
-                notifyViewAccessibilityStateChangedIfNeeded(
+                notifyAccessibilityStateChanged(
                         AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
             }
         }
@@ -11405,6 +11443,7 @@
      * {@link #getAccessibilityLiveRegion()} is not
      * {@link #ACCESSIBILITY_LIVE_REGION_NONE}.
      * </ul>
+     * <li>Has an accessibility pane title, see {@link #setAccessibilityPaneTitle}</li>
      * </ol>
      *
      * @return Whether the view is exposed for accessibility.
@@ -11431,7 +11470,8 @@
 
         return mode == IMPORTANT_FOR_ACCESSIBILITY_YES || isActionableForAccessibility()
                 || hasListenersForAccessibility() || getAccessibilityNodeProvider() != null
-                || getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE;
+                || getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE
+                || (mAccessibilityPaneTitle != null);
     }
 
     /**
@@ -11521,25 +11561,8 @@
      *
      * @hide
      */
-    public void notifyViewAccessibilityStateChangedIfNeeded(int changeType) {
-        if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
-            return;
-        }
-        // If this is a live region, we should send a subtree change event
-        // from this view immediately. Otherwise, we can let it propagate up.
-        if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) {
-            final AccessibilityEvent event = AccessibilityEvent.obtain();
-            event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-            event.setContentChangeTypes(changeType);
-            sendAccessibilityEventUnchecked(event);
-        } else if (mParent != null) {
-            try {
-                mParent.notifySubtreeAccessibilityStateChanged(this, this, changeType);
-            } catch (AbstractMethodError e) {
-                Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
-                        " does not fully implement ViewParent", e);
-            }
-        }
+    public void notifyAccessibilityStateChanged(int changeType) {
+        notifyAccessibilityStateChanged(this, changeType);
     }
 
     /**
@@ -11553,20 +11576,23 @@
      *
      * @hide
      */
-    public void notifySubtreeAccessibilityStateChangedIfNeeded() {
+    public void notifyAccessibilitySubtreeChanged() {
+        if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) {
+            mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED;
+            notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+        }
+    }
+
+    void notifyAccessibilityStateChanged(View source, int changeType) {
         if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
             return;
         }
-        if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) {
-            mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED;
-            if (mParent != null) {
-                try {
-                    mParent.notifySubtreeAccessibilityStateChanged(
-                            this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
-                } catch (AbstractMethodError e) {
-                    Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
-                            " does not fully implement ViewParent", e);
-                }
+        if (mParent != null) {
+            try {
+                mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType);
+            } catch (AbstractMethodError e) {
+                Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName()
+                        + " does not fully implement ViewParent", e);
             }
         }
     }
@@ -11588,8 +11614,10 @@
     /**
      * Reset the flag indicating the accessibility state of the subtree rooted
      * at this view changed.
+     *
+     * @hide
      */
-    void resetSubtreeAccessibilityStateChanged() {
+    public void resetSubtreeAccessibilityStateChanged() {
         mPrivateFlags2 &= ~PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED;
     }
 
@@ -11750,7 +11778,7 @@
                         || getAccessibilitySelectionEnd() != end)
                         && (start == end)) {
                     setAccessibilitySelection(start, end);
-                    notifyViewAccessibilityStateChangedIfNeeded(
+                    notifyAccessibilityStateChanged(
                             AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
                     return true;
                 }
@@ -13744,7 +13772,7 @@
                         ((!(mParent instanceof ViewGroup)) || ((ViewGroup) mParent).isShown())) {
                     dispatchVisibilityAggregated(newVisibility == VISIBLE);
                 }
-                notifySubtreeAccessibilityStateChangedIfNeeded();
+                notifyAccessibilitySubtreeChanged();
             }
         }
 
@@ -13790,13 +13818,13 @@
                     || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0
                     || (changed & CONTEXT_CLICKABLE) != 0) {
                 if (oldIncludeForAccessibility != includeForAccessibility()) {
-                    notifySubtreeAccessibilityStateChangedIfNeeded();
+                    notifyAccessibilitySubtreeChanged();
                 } else {
-                    notifyViewAccessibilityStateChangedIfNeeded(
+                    notifyAccessibilityStateChanged(
                             AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
                 }
             } else if ((changed & ENABLED_MASK) != 0) {
-                notifyViewAccessibilityStateChangedIfNeeded(
+                notifyAccessibilityStateChanged(
                         AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
             }
         }
@@ -13831,10 +13859,13 @@
      * @param oldt Previous vertical scroll origin.
      */
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
-        notifySubtreeAccessibilityStateChangedIfNeeded();
+        notifyAccessibilitySubtreeChanged();
 
-        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
-            postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt);
+        ViewRootImpl root = getViewRootImpl();
+        if (root != null) {
+            root.getAccessibilityState()
+                    .getSendViewScrolledAccessibilityEvent()
+                    .post(this, /* dx */ l - oldl, /* dy */ t - oldt);
         }
 
         mBackgroundSizeChanged = true;
@@ -14230,7 +14261,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifySubtreeAccessibilityStateChangedIfNeeded();
+            notifyAccessibilitySubtreeChanged();
         }
     }
 
@@ -14274,7 +14305,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifySubtreeAccessibilityStateChangedIfNeeded();
+            notifyAccessibilitySubtreeChanged();
         }
     }
 
@@ -14318,7 +14349,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifySubtreeAccessibilityStateChangedIfNeeded();
+            notifyAccessibilitySubtreeChanged();
         }
     }
 
@@ -14355,7 +14386,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifySubtreeAccessibilityStateChangedIfNeeded();
+            notifyAccessibilitySubtreeChanged();
         }
     }
 
@@ -14392,7 +14423,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifySubtreeAccessibilityStateChangedIfNeeded();
+            notifyAccessibilitySubtreeChanged();
         }
     }
 
@@ -14595,7 +14626,7 @@
         if (mTransformationInfo.mAlpha != alpha) {
             // Report visibility changes, which can affect children, to accessibility
             if ((alpha == 0) ^ (mTransformationInfo.mAlpha == 0)) {
-                notifySubtreeAccessibilityStateChangedIfNeeded();
+                notifyAccessibilitySubtreeChanged();
             }
             mTransformationInfo.mAlpha = alpha;
             if (onSetAlpha((int) (alpha * 255))) {
@@ -15097,7 +15128,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifySubtreeAccessibilityStateChangedIfNeeded();
+            notifyAccessibilitySubtreeChanged();
         }
     }
 
@@ -15131,7 +15162,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
-            notifySubtreeAccessibilityStateChangedIfNeeded();
+            notifyAccessibilitySubtreeChanged();
         }
     }
 
@@ -15301,7 +15332,7 @@
     public void invalidateOutline() {
         rebuildOutline();
 
-        notifySubtreeAccessibilityStateChangedIfNeeded();
+        notifyAccessibilitySubtreeChanged();
         invalidateViewProperty(false, false);
     }
 
@@ -15496,7 +15527,7 @@
                 }
                 invalidateParentIfNeeded();
             }
-            notifySubtreeAccessibilityStateChangedIfNeeded();
+            notifyAccessibilitySubtreeChanged();
         }
     }
 
@@ -15544,7 +15575,7 @@
                 }
                 invalidateParentIfNeeded();
             }
-            notifySubtreeAccessibilityStateChangedIfNeeded();
+            notifyAccessibilitySubtreeChanged();
         }
     }
 
@@ -16422,18 +16453,6 @@
     }
 
     /**
-     * Post a callback to send a {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
-     * This event is sent at most once every
-     * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
-     */
-    private void postSendViewScrolledAccessibilityEventCallback(int dx, int dy) {
-        if (mSendViewScrolledAccessibilityEvent == null) {
-            mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent();
-        }
-        mSendViewScrolledAccessibilityEvent.post(dx, dy);
-    }
-
-    /**
      * Called by a parent to request that a child update its values for mScrollX
      * and mScrollY if necessary. This will typically be done if the child is
      * animating a scroll using a {@link android.widget.Scroller Scroller}
@@ -17688,7 +17707,13 @@
         removeUnsetPressCallback();
         removeLongPressCallback();
         removePerformClickCallback();
-        cancel(mSendViewScrolledAccessibilityEvent);
+        if (mAttachInfo != null
+                && mAttachInfo.mViewRootImpl.mAccessibilityState != null
+                && mAttachInfo.mViewRootImpl.mAccessibilityState.isScrollEventSenderInitialized()) {
+            mAttachInfo.mViewRootImpl.mAccessibilityState
+                    .getSendViewScrolledAccessibilityEvent()
+                    .cancelIfPendingFor(this);
+        }
         stopNestedScroll();
 
         // Anything that started animating right before detach should already
@@ -20307,7 +20332,7 @@
                 mForegroundInfo.mBoundsChanged = true;
             }
 
-            notifySubtreeAccessibilityStateChangedIfNeeded();
+            notifyAccessibilitySubtreeChanged();
         }
         return changed;
     }
@@ -21751,7 +21776,7 @@
             if (selected) {
                 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
             } else {
-                notifyViewAccessibilityStateChangedIfNeeded(
+                notifyAccessibilityStateChanged(
                         AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
             }
         }
@@ -26308,53 +26333,6 @@
     }
 
     /**
-     * Resuable callback for sending
-     * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
-     */
-    private class SendViewScrolledAccessibilityEvent implements Runnable {
-        public volatile boolean mIsPending;
-        public int mDeltaX;
-        public int mDeltaY;
-
-        public void post(int dx, int dy) {
-            mDeltaX += dx;
-            mDeltaY += dy;
-            if (!mIsPending) {
-                mIsPending = true;
-                postDelayed(this, ViewConfiguration.getSendRecurringAccessibilityEventsInterval());
-            }
-        }
-
-        @Override
-        public void run() {
-            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
-                AccessibilityEvent event = AccessibilityEvent.obtain(
-                        AccessibilityEvent.TYPE_VIEW_SCROLLED);
-                event.setScrollDeltaX(mDeltaX);
-                event.setScrollDeltaY(mDeltaY);
-                sendAccessibilityEventUnchecked(event);
-            }
-            reset();
-        }
-
-        private void reset() {
-            mIsPending = false;
-            mDeltaX = 0;
-            mDeltaY = 0;
-        }
-    }
-
-    /**
-     * Remove the pending callback for sending a
-     * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
-     */
-    private void cancel(@Nullable SendViewScrolledAccessibilityEvent callback) {
-        if (callback == null || !callback.mIsPending) return;
-        removeCallbacks(callback);
-        callback.reset();
-    }
-
-    /**
      * <p>
      * This class represents a delegate that can be registered in a {@link View}
      * to enhance accessibility support via composition rather via inheritance.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 703364f..e0864bd 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3646,44 +3646,34 @@
         return ViewGroup.class.getName();
     }
 
-    @Override
-    public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
-        // If this is a live region, we should send a subtree change event
-        // from this view. Otherwise, we can let it propagate up.
-        if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) {
-            notifyViewAccessibilityStateChangedIfNeeded(
-                    AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
-        } else if (mParent != null) {
-            try {
-                mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType);
-            } catch (AbstractMethodError e) {
-                Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
-                        " does not fully implement ViewParent", e);
-            }
-        }
-    }
-
     /** @hide */
     @Override
-    public void notifySubtreeAccessibilityStateChangedIfNeeded() {
+    public void notifyAccessibilitySubtreeChanged() {
         if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
             return;
         }
         // If something important for a11y is happening in this subtree, make sure it's dispatched
         // from a view that is important for a11y so it doesn't get lost.
-        if ((getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
-                && !isImportantForAccessibility() && (getChildCount() > 0)) {
+        if (getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                && !isImportantForAccessibility()
+                && getChildCount() > 0) {
             ViewParent a11yParent = getParentForAccessibility();
             if (a11yParent instanceof View) {
-                ((View) a11yParent).notifySubtreeAccessibilityStateChangedIfNeeded();
+                ((View) a11yParent).notifyAccessibilitySubtreeChanged();
                 return;
             }
         }
-        super.notifySubtreeAccessibilityStateChangedIfNeeded();
+        super.notifyAccessibilitySubtreeChanged();
     }
 
     @Override
-    void resetSubtreeAccessibilityStateChanged() {
+    public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
+        notifyAccessibilityStateChanged(source, changeType);
+    }
+
+    /** @hide */
+    @Override
+    public void resetSubtreeAccessibilityStateChanged() {
         super.resetSubtreeAccessibilityStateChanged();
         View[] children = mChildren;
         final int childCount = mChildrenCount;
@@ -5095,7 +5085,7 @@
         }
 
         if (child.getVisibility() != View.GONE) {
-            notifySubtreeAccessibilityStateChangedIfNeeded();
+            notifyAccessibilitySubtreeChanged();
         }
 
         if (mTransientIndices != null) {
@@ -5365,7 +5355,7 @@
         dispatchViewRemoved(view);
 
         if (view.getVisibility() != View.GONE) {
-            notifySubtreeAccessibilityStateChangedIfNeeded();
+            notifyAccessibilitySubtreeChanged();
         }
 
         int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
@@ -6084,7 +6074,7 @@
         if (invalidate) {
             invalidateViewProperty(false, false);
         }
-        notifySubtreeAccessibilityStateChangedIfNeeded();
+        notifyAccessibilitySubtreeChanged();
     }
 
     @Override
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6c5091c..f81a4c3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -89,9 +89,11 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.AccessibilityViewHierarchyState;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.view.accessibility.ThrottlingAccessibilityEventSender;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.view.inputmethod.InputMethodManager;
@@ -113,7 +115,6 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -460,10 +461,6 @@
             new AccessibilityInteractionConnectionManager();
     final HighContrastTextManager mHighContrastTextManager;
 
-    SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
-
-    HashSet<View> mTempHashSet;
-
     private final int mDensity;
     private final int mNoncompatDensity;
 
@@ -478,6 +475,8 @@
 
     private boolean mNeedsRendererSetup;
 
+    protected AccessibilityViewHierarchyState mAccessibilityState;
+
     /**
      * Consistency verifier for debugging purposes.
      */
@@ -7262,11 +7261,9 @@
      * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
      */
     private void postSendWindowContentChangedCallback(View source, int changeType) {
-        if (mSendWindowContentChangedAccessibilityEvent == null) {
-            mSendWindowContentChangedAccessibilityEvent =
-                new SendWindowContentChangedAccessibilityEvent();
-        }
-        mSendWindowContentChangedAccessibilityEvent.runOrPost(source, changeType);
+        getAccessibilityState()
+                .getSendWindowContentChangedAccessibilityEvent()
+                .runOrPost(source, changeType);
     }
 
     /**
@@ -7274,11 +7271,20 @@
      * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
      */
     private void removeSendWindowContentChangedCallback() {
-        if (mSendWindowContentChangedAccessibilityEvent != null) {
-            mHandler.removeCallbacks(mSendWindowContentChangedAccessibilityEvent);
+        if (mAccessibilityState != null
+                && mAccessibilityState.isWindowContentChangedEventSenderInitialized()) {
+            ThrottlingAccessibilityEventSender.cancelIfPending(
+                    mAccessibilityState.getSendWindowContentChangedAccessibilityEvent());
         }
     }
 
+    AccessibilityViewHierarchyState getAccessibilityState() {
+        if (mAccessibilityState == null) {
+            mAccessibilityState = new AccessibilityViewHierarchyState();
+        }
+        return mAccessibilityState;
+    }
+
     @Override
     public boolean showContextMenuForChild(View originalView) {
         return false;
@@ -7314,12 +7320,8 @@
             return false;
         }
 
-        // Immediately flush pending content changed event (if any) to preserve event order
-        if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
-                && mSendWindowContentChangedAccessibilityEvent != null
-                && mSendWindowContentChangedAccessibilityEvent.mSource != null) {
-            mSendWindowContentChangedAccessibilityEvent.removeCallbacksAndRun();
-        }
+        // Send any pending event to prevent reordering
+        flushPendingAccessibilityEvents();
 
         // Intercept accessibility focus events fired by virtual nodes to keep
         // track of accessibility focus position in such nodes.
@@ -7363,6 +7365,19 @@
         return true;
     }
 
+    /** @hide */
+    public void flushPendingAccessibilityEvents() {
+        if (mAccessibilityState != null) {
+            if (mAccessibilityState.isScrollEventSenderInitialized()) {
+                mAccessibilityState.getSendViewScrolledAccessibilityEvent().sendNowIfPending();
+            }
+            if (mAccessibilityState.isWindowContentChangedEventSenderInitialized()) {
+                mAccessibilityState.getSendWindowContentChangedAccessibilityEvent()
+                        .sendNowIfPending();
+            }
+        }
+    }
+
     /**
      * Updates the focused virtual view, when necessary, in response to a
      * content changed event.
@@ -7497,39 +7512,6 @@
         return View.TEXT_ALIGNMENT_RESOLVED_DEFAULT;
     }
 
-    private View getCommonPredecessor(View first, View second) {
-        if (mTempHashSet == null) {
-            mTempHashSet = new HashSet<View>();
-        }
-        HashSet<View> seen = mTempHashSet;
-        seen.clear();
-        View firstCurrent = first;
-        while (firstCurrent != null) {
-            seen.add(firstCurrent);
-            ViewParent firstCurrentParent = firstCurrent.mParent;
-            if (firstCurrentParent instanceof View) {
-                firstCurrent = (View) firstCurrentParent;
-            } else {
-                firstCurrent = null;
-            }
-        }
-        View secondCurrent = second;
-        while (secondCurrent != null) {
-            if (seen.contains(secondCurrent)) {
-                seen.clear();
-                return secondCurrent;
-            }
-            ViewParent secondCurrentParent = secondCurrent.mParent;
-            if (secondCurrentParent instanceof View) {
-                secondCurrent = (View) secondCurrentParent;
-            } else {
-                secondCurrent = null;
-            }
-        }
-        seen.clear();
-        return null;
-    }
-
     void checkThread() {
         if (mThread != Thread.currentThread()) {
             throw new CalledFromWrongThreadException(
@@ -8140,80 +8122,6 @@
         }
     }
 
-    private class SendWindowContentChangedAccessibilityEvent implements Runnable {
-        private int mChangeTypes = 0;
-
-        public View mSource;
-        public long mLastEventTimeMillis;
-
-        @Override
-        public void run() {
-            // Protect against re-entrant code and attempt to do the right thing in the case that
-            // we're multithreaded.
-            View source = mSource;
-            mSource = null;
-            if (source == null) {
-                Log.e(TAG, "Accessibility content change has no source");
-                return;
-            }
-            // The accessibility may be turned off while we were waiting so check again.
-            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
-                mLastEventTimeMillis = SystemClock.uptimeMillis();
-                AccessibilityEvent event = AccessibilityEvent.obtain();
-                event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-                event.setContentChangeTypes(mChangeTypes);
-                source.sendAccessibilityEventUnchecked(event);
-            } else {
-                mLastEventTimeMillis = 0;
-            }
-            // In any case reset to initial state.
-            source.resetSubtreeAccessibilityStateChanged();
-            mChangeTypes = 0;
-        }
-
-        public void runOrPost(View source, int changeType) {
-            if (mHandler.getLooper() != Looper.myLooper()) {
-                CalledFromWrongThreadException e = new CalledFromWrongThreadException("Only the "
-                        + "original thread that created a view hierarchy can touch its views.");
-                // TODO: Throw the exception
-                Log.e(TAG, "Accessibility content change on non-UI thread. Future Android "
-                        + "versions will throw an exception.", e);
-                // Attempt to recover. This code does not eliminate the thread safety issue, but
-                // it should force any issues to happen near the above log.
-                mHandler.removeCallbacks(this);
-                if (mSource != null) {
-                    // Dispatch whatever was pending. It's still possible that the runnable started
-                    // just before we removed the callbacks, and bad things will happen, but at
-                    // least they should happen very close to the logged error.
-                    run();
-                }
-            }
-            if (mSource != null) {
-                // If there is no common predecessor, then mSource points to
-                // a removed view, hence in this case always prefer the source.
-                View predecessor = getCommonPredecessor(mSource, source);
-                mSource = (predecessor != null) ? predecessor : source;
-                mChangeTypes |= changeType;
-                return;
-            }
-            mSource = source;
-            mChangeTypes = changeType;
-            final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis;
-            final long minEventIntevalMillis =
-                    ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
-            if (timeSinceLastMillis >= minEventIntevalMillis) {
-                removeCallbacksAndRun();
-            } else {
-                mHandler.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis);
-            }
-        }
-
-        public void removeCallbacksAndRun() {
-            mHandler.removeCallbacks(this);
-            run();
-        }
-    }
-
     private static class KeyFallbackManager {
 
         // This is used to ensure that key-fallback events are only dispatched once. We attempt
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 6c2d349..aa61926 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -565,6 +565,12 @@
     public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
 
     /**
+     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * The node's pane title changed.
+     */
+    public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 0x00000008;
+
+    /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window was added.
      */
@@ -654,7 +660,8 @@
                     CONTENT_CHANGE_TYPE_UNDEFINED,
                     CONTENT_CHANGE_TYPE_SUBTREE,
                     CONTENT_CHANGE_TYPE_TEXT,
-                    CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION
+                    CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION,
+                    CONTENT_CHANGE_TYPE_PANE_TITLE
             })
     public @interface ContentChangeTypes {}
 
@@ -1244,43 +1251,33 @@
         builder.append("EventType: ").append(eventTypeToString(mEventType));
         builder.append("; EventTime: ").append(mEventTime);
         builder.append("; PackageName: ").append(mPackageName);
-        builder.append("; MovementGranularity: ").append(mMovementGranularity);
-        builder.append("; Action: ").append(mAction);
-        builder.append("; ContentChangeTypes: ").append(
-                contentChangeTypesToString(mContentChangeTypes));
-        builder.append("; WindowChangeTypes: ").append(
-                windowChangeTypesToString(mWindowChangeTypes));
-        builder.append(super.toString());
-        if (DEBUG) {
-            builder.append("\n");
-            builder.append("; sourceWindowId: ").append(mSourceWindowId);
-            builder.append("; mSourceNodeId: ").append(mSourceNodeId);
-            for (int i = 0; i < getRecordCount(); i++) {
-                final AccessibilityRecord record = getRecord(i);
-                builder.append("  Record ");
-                builder.append(i);
-                builder.append(":");
-                builder.append(" [ ClassName: " + record.mClassName);
-                builder.append("; Text: " + record.mText);
-                builder.append("; ContentDescription: " + record.mContentDescription);
-                builder.append("; ItemCount: " + record.mItemCount);
-                builder.append("; CurrentItemIndex: " + record.mCurrentItemIndex);
-                builder.append("; IsEnabled: " + record.isEnabled());
-                builder.append("; IsPassword: " + record.isPassword());
-                builder.append("; IsChecked: " + record.isChecked());
-                builder.append("; IsFullScreen: " + record.isFullScreen());
-                builder.append("; Scrollable: " + record.isScrollable());
-                builder.append("; BeforeText: " + record.mBeforeText);
-                builder.append("; FromIndex: " + record.mFromIndex);
-                builder.append("; ToIndex: " + record.mToIndex);
-                builder.append("; ScrollX: " + record.mScrollX);
-                builder.append("; ScrollY: " + record.mScrollY);
-                builder.append("; AddedCount: " + record.mAddedCount);
-                builder.append("; RemovedCount: " + record.mRemovedCount);
-                builder.append("; ParcelableData: " + record.mParcelableData);
-                builder.append(" ]");
+        if (!DEBUG_CONCISE_TOSTRING || mMovementGranularity != 0) {
+            builder.append("; MovementGranularity: ").append(mMovementGranularity);
+        }
+        if (!DEBUG_CONCISE_TOSTRING || mAction != 0) {
+            builder.append("; Action: ").append(mAction);
+        }
+        if (!DEBUG_CONCISE_TOSTRING || mContentChangeTypes != 0) {
+            builder.append("; ContentChangeTypes: ").append(
+                    contentChangeTypesToString(mContentChangeTypes));
+        }
+        if (!DEBUG_CONCISE_TOSTRING || mWindowChangeTypes != 0) {
+            builder.append("; WindowChangeTypes: ").append(
+                    contentChangeTypesToString(mWindowChangeTypes));
+        }
+        super.appendTo(builder);
+        if (DEBUG || DEBUG_CONCISE_TOSTRING) {
+            if (!DEBUG_CONCISE_TOSTRING) {
                 builder.append("\n");
             }
+            if (DEBUG) {
+                builder.append("; SourceWindowId: ").append(mSourceWindowId);
+                builder.append("; SourceNodeId: ").append(mSourceNodeId);
+            }
+            for (int i = 0; i < getRecordCount(); i++) {
+                builder.append("  Record ").append(i).append(":");
+                getRecord(i).appendTo(builder).append("\n");
+            }
         } else {
             builder.append("; recordCount: ").append(getRecordCount());
         }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 28ef697..311dd4b 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -723,6 +723,7 @@
     private CharSequence mText;
     private CharSequence mHintText;
     private CharSequence mError;
+    private CharSequence mPaneTitle;
     private CharSequence mContentDescription;
     private String mViewIdResourceName;
     private ArrayList<String> mExtraDataKeys;
@@ -2033,6 +2034,33 @@
     }
 
     /**
+     * If this node represents a visually distinct region of the screen that may update separately
+     * from the rest of the window, it is considered a pane. Set the pane title to indicate that
+     * the node is a pane, and to provide a title for it.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     * @param paneTitle The title of the pane represented by this node.
+     */
+    public void setPaneTitle(@Nullable CharSequence paneTitle) {
+        enforceNotSealed();
+        mPaneTitle = (paneTitle == null)
+                ? null : paneTitle.subSequence(0, paneTitle.length());
+    }
+
+    /**
+     * Get the title of the pane represented by this node.
+     *
+     * @return The title of the pane represented by this node, or {@code null} if this node does
+     *         not represent a pane.
+     */
+    public @Nullable CharSequence getPaneTitle() {
+        return mPaneTitle;
+    }
+
+    /**
      * Get the drawing order of the view corresponding it this node.
      * <p>
      * Drawing order is determined only within the node's parent, so this index is only relative
@@ -3151,6 +3179,10 @@
             nonDefaultFields |= bitAt(fieldIndex);
         }
         fieldIndex++;
+        if (!Objects.equals(mPaneTitle, DEFAULT.mPaneTitle)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
         if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) {
             nonDefaultFields |= bitAt(fieldIndex);
         }
@@ -3270,6 +3302,7 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
             parcel.writeCharSequence(mContentDescription);
         }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName);
 
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart);
@@ -3341,6 +3374,7 @@
         mHintText = other.mHintText;
         mError = other.mError;
         mContentDescription = other.mContentDescription;
+        mPaneTitle = other.mPaneTitle;
         mViewIdResourceName = other.mViewIdResourceName;
 
         if (mActions != null) mActions.clear();
@@ -3461,6 +3495,7 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
             mContentDescription = parcel.readCharSequence();
         }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readString();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
 
         if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionStart = parcel.readInt();
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index fa505c9..0a709f8 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -16,6 +16,8 @@
 
 package android.view.accessibility;
 
+import static com.android.internal.util.CollectionUtils.isEmpty;
+
 import android.annotation.Nullable;
 import android.os.Parcelable;
 import android.view.View;
@@ -55,6 +57,8 @@
  * @see AccessibilityNodeInfo
  */
 public class AccessibilityRecord {
+    /** @hide */
+    protected static final boolean DEBUG_CONCISE_TOSTRING = false;
 
     private static final int UNDEFINED = -1;
 
@@ -888,28 +892,69 @@
 
     @Override
     public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append(" [ ClassName: " + mClassName);
-        builder.append("; Text: " + mText);
-        builder.append("; ContentDescription: " + mContentDescription);
-        builder.append("; ItemCount: " + mItemCount);
-        builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
-        builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED));
-        builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD));
-        builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED));
-        builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN));
-        builder.append("; Scrollable: " + getBooleanProperty(PROPERTY_SCROLLABLE));
-        builder.append("; BeforeText: " + mBeforeText);
-        builder.append("; FromIndex: " + mFromIndex);
-        builder.append("; ToIndex: " + mToIndex);
-        builder.append("; ScrollX: " + mScrollX);
-        builder.append("; ScrollY: " + mScrollY);
-        builder.append("; MaxScrollX: " + mMaxScrollX);
-        builder.append("; MaxScrollY: " + mMaxScrollY);
-        builder.append("; AddedCount: " + mAddedCount);
-        builder.append("; RemovedCount: " + mRemovedCount);
-        builder.append("; ParcelableData: " + mParcelableData);
+        return appendTo(new StringBuilder()).toString();
+    }
+
+    StringBuilder appendTo(StringBuilder builder) {
+        builder.append(" [ ClassName: ").append(mClassName);
+        if (!DEBUG_CONCISE_TOSTRING || !isEmpty(mText)) {
+            appendPropName(builder, "Text").append(mText);
+        }
+        append(builder, "ContentDescription", mContentDescription);
+        append(builder, "ItemCount", mItemCount);
+        append(builder, "CurrentItemIndex", mCurrentItemIndex);
+
+        appendUnless(true, PROPERTY_ENABLED, builder);
+        appendUnless(false, PROPERTY_PASSWORD, builder);
+        appendUnless(false, PROPERTY_CHECKED, builder);
+        appendUnless(false, PROPERTY_FULL_SCREEN, builder);
+        appendUnless(false, PROPERTY_SCROLLABLE, builder);
+
+        append(builder, "BeforeText", mBeforeText);
+        append(builder, "FromIndex", mFromIndex);
+        append(builder, "ToIndex", mToIndex);
+        append(builder, "ScrollX", mScrollX);
+        append(builder, "ScrollY", mScrollY);
+        append(builder, "MaxScrollX", mMaxScrollX);
+        append(builder, "MaxScrollY", mMaxScrollY);
+        append(builder, "AddedCount", mAddedCount);
+        append(builder, "RemovedCount", mRemovedCount);
+        append(builder, "ParcelableData", mParcelableData);
         builder.append(" ]");
-        return builder.toString();
+        return builder;
+    }
+
+    private void appendUnless(boolean defValue, int prop, StringBuilder builder) {
+        boolean value = getBooleanProperty(prop);
+        if (DEBUG_CONCISE_TOSTRING && value == defValue) return;
+        appendPropName(builder, singleBooleanPropertyToString(prop))
+                .append(value);
+    }
+
+    private static String singleBooleanPropertyToString(int prop) {
+        switch (prop) {
+            case PROPERTY_CHECKED: return "Checked";
+            case PROPERTY_ENABLED: return "Enabled";
+            case PROPERTY_PASSWORD: return "Password";
+            case PROPERTY_FULL_SCREEN: return "FullScreen";
+            case PROPERTY_SCROLLABLE: return "Scrollable";
+            case PROPERTY_IMPORTANT_FOR_ACCESSIBILITY:
+                return "ImportantForAccessibility";
+            default: return Integer.toHexString(prop);
+        }
+    }
+
+    private void append(StringBuilder builder, String propName, int propValue) {
+        if (DEBUG_CONCISE_TOSTRING && propValue == UNDEFINED) return;
+        appendPropName(builder, propName).append(propValue);
+    }
+
+    private void append(StringBuilder builder, String propName, Object propValue) {
+        if (DEBUG_CONCISE_TOSTRING && propValue == null) return;
+        appendPropName(builder, propName).append(propValue);
+    }
+
+    private StringBuilder appendPropName(StringBuilder builder, String propName) {
+        return builder.append("; ").append(propName).append(": ");
     }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java b/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java
new file mode 100644
index 0000000..447fafa
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java
@@ -0,0 +1,61 @@
+/*
+ * 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 android.view.accessibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Accessibility-related state of a {@link android.view.ViewRootImpl}
+ *
+ * @hide
+ */
+public class AccessibilityViewHierarchyState {
+    private @Nullable SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent;
+    private @Nullable SendWindowContentChangedAccessibilityEvent
+            mSendWindowContentChangedAccessibilityEvent;
+
+    /**
+     * @return a {@link SendViewScrolledAccessibilityEvent}, creating one if needed
+     */
+    public @NonNull SendViewScrolledAccessibilityEvent getSendViewScrolledAccessibilityEvent() {
+        if (mSendViewScrolledAccessibilityEvent == null) {
+            mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent();
+        }
+        return mSendViewScrolledAccessibilityEvent;
+    }
+
+    public boolean isScrollEventSenderInitialized() {
+        return mSendViewScrolledAccessibilityEvent != null;
+    }
+
+    /**
+     * @return a {@link SendWindowContentChangedAccessibilityEvent}, creating one if needed
+     */
+    public @NonNull SendWindowContentChangedAccessibilityEvent
+            getSendWindowContentChangedAccessibilityEvent() {
+        if (mSendWindowContentChangedAccessibilityEvent == null) {
+            mSendWindowContentChangedAccessibilityEvent =
+                    new SendWindowContentChangedAccessibilityEvent();
+        }
+        return mSendWindowContentChangedAccessibilityEvent;
+    }
+
+    public boolean isWindowContentChangedEventSenderInitialized() {
+        return mSendWindowContentChangedAccessibilityEvent != null;
+    }
+}
diff --git a/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java b/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java
new file mode 100644
index 0000000..40a1b6a
--- /dev/null
+++ b/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java
@@ -0,0 +1,58 @@
+/*
+ * 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 android.view.accessibility;
+
+
+import android.annotation.NonNull;
+import android.view.View;
+
+/**
+ * Sender for {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
+ *
+ * @hide
+ */
+public class SendViewScrolledAccessibilityEvent extends ThrottlingAccessibilityEventSender {
+
+    public int mDeltaX;
+    public int mDeltaY;
+
+    /**
+     * Post a scroll event to be sent for the given view
+     */
+    public void post(View source, int dx, int dy) {
+        if (!isPendingFor(source)) sendNowIfPending();
+
+        mDeltaX += dx;
+        mDeltaY += dy;
+
+        if (!isPendingFor(source)) scheduleFor(source);
+    }
+
+    @Override
+    protected void performSendEvent(@NonNull View source) {
+        AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+        event.setScrollDeltaX(mDeltaX);
+        event.setScrollDeltaY(mDeltaY);
+        source.sendAccessibilityEventUnchecked(event);
+    }
+
+    @Override
+    protected void resetState(@NonNull View source) {
+        mDeltaX = 0;
+        mDeltaY = 0;
+    }
+}
diff --git a/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java b/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java
new file mode 100644
index 0000000..df38fba
--- /dev/null
+++ b/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java
@@ -0,0 +1,111 @@
+/*
+ * 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 android.view.accessibility;
+
+
+import static com.android.internal.util.ObjectUtils.firstNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.View;
+import android.view.ViewParent;
+
+import java.util.HashSet;
+
+/**
+ * @hide
+ */
+public class SendWindowContentChangedAccessibilityEvent
+        extends ThrottlingAccessibilityEventSender {
+
+    private int mChangeTypes = 0;
+
+    private HashSet<View> mTempHashSet;
+
+    @Override
+    protected void performSendEvent(@NonNull View source) {
+        AccessibilityEvent event = AccessibilityEvent.obtain();
+        event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+        event.setContentChangeTypes(mChangeTypes);
+        source.sendAccessibilityEventUnchecked(event);
+    }
+
+    @Override
+    protected void resetState(@Nullable View source) {
+        if (source != null) {
+            source.resetSubtreeAccessibilityStateChanged();
+        }
+        mChangeTypes = 0;
+    }
+
+    /**
+     * Post the {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with the given
+     * {@link AccessibilityEvent#getContentChangeTypes change type} for the given view
+     */
+    public void runOrPost(View source, int changeType) {
+        if (source.getAccessibilityLiveRegion() != View.ACCESSIBILITY_LIVE_REGION_NONE) {
+            sendNowIfPending();
+            mChangeTypes = changeType;
+            sendNow(source);
+        } else {
+            mChangeTypes |= changeType;
+            scheduleFor(source);
+        }
+    }
+
+    @Override
+    protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) {
+        // If there is no common predecessor, then oldSource points to
+        // a removed view, hence in this case always prefer the newSource.
+        return firstNotNull(
+                getCommonPredecessor(oldSource, newSource),
+                newSource);
+    }
+
+    private View getCommonPredecessor(View first, View second) {
+        if (mTempHashSet == null) {
+            mTempHashSet = new HashSet<>();
+        }
+        HashSet<View> seen = mTempHashSet;
+        seen.clear();
+        View firstCurrent = first;
+        while (firstCurrent != null) {
+            seen.add(firstCurrent);
+            ViewParent firstCurrentParent = firstCurrent.getParent();
+            if (firstCurrentParent instanceof View) {
+                firstCurrent = (View) firstCurrentParent;
+            } else {
+                firstCurrent = null;
+            }
+        }
+        View secondCurrent = second;
+        while (secondCurrent != null) {
+            if (seen.contains(secondCurrent)) {
+                seen.clear();
+                return secondCurrent;
+            }
+            ViewParent secondCurrentParent = secondCurrent.getParent();
+            if (secondCurrentParent instanceof View) {
+                secondCurrent = (View) secondCurrentParent;
+            } else {
+                secondCurrent = null;
+            }
+        }
+        seen.clear();
+        return null;
+    }
+}
diff --git a/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java b/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java
new file mode 100644
index 0000000..66fa301
--- /dev/null
+++ b/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java
@@ -0,0 +1,248 @@
+/*
+ * 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 android.view.accessibility;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewRootImpl;
+import android.view.ViewRootImpl.CalledFromWrongThreadException;
+
+/**
+ * A throttling {@link AccessibilityEvent} sender that relies on its currently associated
+ * 'source' view's {@link View#postDelayed delayed execution} to delay and possibly
+ * {@link #tryMerge merge} together any events that come in less than
+ * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval
+ * the configured amount of milliseconds} apart.
+ *
+ * The suggested usage is to create a singleton extending this class, holding any state specific to
+ * the particular event type that the subclass represents, and have an 'entrypoint' method that
+ * delegates to {@link #scheduleFor(View)}.
+ * For example:
+ *
+ * {@code
+ *     public void post(View view, String text, int resId) {
+ *         mText = text;
+ *         mId = resId;
+ *         scheduleFor(view);
+ *     }
+ * }
+ *
+ * @see #scheduleFor(View)
+ * @see #tryMerge(View, View)
+ * @see #performSendEvent(View)
+ * @hide
+ */
+public abstract class ThrottlingAccessibilityEventSender {
+
+    private static final boolean DEBUG = false;
+    private static final String LOG_TAG = "ThrottlingA11ySender";
+
+    View mSource;
+    private long mLastSendTimeMillis = Long.MIN_VALUE;
+    private boolean mIsPending = false;
+
+    private final Runnable mWorker = () -> {
+        View source = mSource;
+        if (DEBUG) Log.d(LOG_TAG, thisClass() + ".run(mSource = " + source + ")");
+
+        if (!checkAndResetIsPending() || source == null) {
+            resetStateInternal();
+            return;
+        }
+
+        // Accessibility may be turned off while we were waiting
+        if (isAccessibilityEnabled(source)) {
+            mLastSendTimeMillis = SystemClock.uptimeMillis();
+            performSendEvent(source);
+        }
+        resetStateInternal();
+    };
+
+    /**
+     * Populate and send an {@link AccessibilityEvent} using the given {@code source} view, as well
+     * as any extra data from this instance's state.
+     *
+     * Send the event via {@link View#sendAccessibilityEventUnchecked(AccessibilityEvent)} or
+     * {@link View#sendAccessibilityEvent(int)} on the provided {@code source} view to allow for
+     * overrides of those methods on {@link View} subclasses to take effect, and/or make sure that
+     * an {@link View#getAccessibilityDelegate() accessibility delegate} is not ignored if any.
+     */
+    protected abstract void performSendEvent(@NonNull View source);
+
+    /**
+     * Perform optional cleanup after {@link #performSendEvent}
+     *
+     * @param source the view this event was associated with
+     */
+    protected abstract void resetState(@Nullable View source);
+
+    /**
+     * Attempt to merge the pending events for source views {@code oldSource} and {@code newSource}
+     * into one, with source set to the resulting {@link View}
+     *
+     * A result of {@code null} means merger is not possible, resulting in the currently pending
+     * event being flushed before proceeding.
+     */
+    protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) {
+        return null;
+    }
+
+    /**
+     * Schedules a {@link #performSendEvent} with the source {@link View} set to given
+     * {@code source}
+     *
+     * If an event is already scheduled a {@link #tryMerge merge} will be attempted.
+     * If merging is not possible (as indicated by the null result from {@link #tryMerge}),
+     * the currently scheduled event will be {@link #sendNow sent immediately} and the new one
+     * will be scheduled afterwards.
+     */
+    protected final void scheduleFor(@NonNull View source) {
+        if (DEBUG) Log.d(LOG_TAG, thisClass() + ".scheduleFor(source = " + source + ")");
+
+        Handler uiHandler = source.getHandler();
+        if (uiHandler == null || uiHandler.getLooper() != Looper.myLooper()) {
+            CalledFromWrongThreadException e = new CalledFromWrongThreadException(
+                    "Expected to be called from main thread but was called from "
+                            + Thread.currentThread());
+            // TODO: Throw the exception
+            Log.e(LOG_TAG, "Accessibility content change on non-UI thread. Future Android "
+                    + "versions will throw an exception.", e);
+        }
+
+        if (!isAccessibilityEnabled(source)) return;
+
+        if (mIsPending) {
+            View merged = tryMerge(mSource, source);
+            if (merged != null) {
+                setSource(merged);
+                return;
+            } else {
+                sendNow();
+            }
+        }
+
+        setSource(source);
+
+        final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastSendTimeMillis;
+        final long minEventIntervalMillis =
+                ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
+        if (timeSinceLastMillis >= minEventIntervalMillis) {
+            sendNow();
+        } else {
+            mSource.postDelayed(mWorker, minEventIntervalMillis - timeSinceLastMillis);
+        }
+    }
+
+    static boolean isAccessibilityEnabled(@NonNull View contextProvider) {
+        return AccessibilityManager.getInstance(contextProvider.getContext()).isEnabled();
+    }
+
+    protected final void sendNow(View source) {
+        setSource(source);
+        sendNow();
+    }
+
+    private void sendNow() {
+        mSource.removeCallbacks(mWorker);
+        mWorker.run();
+    }
+
+    /**
+     * Flush the event if one is pending
+     */
+    public void sendNowIfPending() {
+        if (mIsPending) sendNow();
+    }
+
+    /**
+     * Cancel the event if one is pending and is for the given view
+     */
+    public final void cancelIfPendingFor(@NonNull View source) {
+        if (isPendingFor(source)) cancelIfPending(this);
+    }
+
+    /**
+     * @return whether an event is currently pending for the given source view
+     */
+    protected final boolean isPendingFor(@Nullable View source) {
+        return mIsPending && mSource == source;
+    }
+
+    /**
+     * Cancel the event if one is not null and pending
+     */
+    public static void cancelIfPending(@Nullable ThrottlingAccessibilityEventSender sender) {
+        if (sender == null || !sender.checkAndResetIsPending()) return;
+        sender.mSource.removeCallbacks(sender.mWorker);
+        sender.resetStateInternal();
+    }
+
+    void resetStateInternal() {
+        if (DEBUG) Log.d(LOG_TAG, thisClass() + ".resetStateInternal()");
+
+        resetState(mSource);
+        setSource(null);
+    }
+
+    boolean checkAndResetIsPending() {
+        if (mIsPending) {
+            mIsPending = false;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private void setSource(@Nullable View source) {
+        if (DEBUG) Log.d(LOG_TAG, thisClass() + ".setSource(" + source + ")");
+
+        if (source == null && mIsPending) {
+            Log.e(LOG_TAG, "mSource nullified while callback still pending: " + this);
+            return;
+        }
+
+        if (source != null && !mIsPending) {
+            // At most one can be pending at any given time
+            View oldSource = mSource;
+            if (oldSource != null) {
+                ViewRootImpl viewRootImpl = oldSource.getViewRootImpl();
+                if (viewRootImpl != null) {
+                    viewRootImpl.flushPendingAccessibilityEvents();
+                }
+            }
+            mIsPending = true;
+        }
+        mSource = source;
+    }
+
+    String thisClass() {
+        return getClass().getSimpleName();
+    }
+
+    @Override
+    public String toString() {
+        return thisClass() + "(" + mSource + ")";
+    }
+
+}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 57f9895..e554540 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -1,17 +1,17 @@
 /*
- * Copyright (C) 2007-2008 The Android Open Source Project
+ * Copyright (C) 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
+ * 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
+ *      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.
+ * 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.view.inputmethod;
@@ -131,13 +131,13 @@
      * spans. <strong>Editor authors</strong>: you should strive to
      * send text with styles if possible, but it is not required.
      */
-    static final int GET_TEXT_WITH_STYLES = 0x0001;
+    int GET_TEXT_WITH_STYLES = 0x0001;
 
     /**
      * Flag for use with {@link #getExtractedText} to indicate you
      * would like to receive updates when the extracted text changes.
      */
-    public static final int GET_EXTRACTED_TEXT_MONITOR = 0x0001;
+    int GET_EXTRACTED_TEXT_MONITOR = 0x0001;
 
     /**
      * Get <var>n</var> characters of text before the current cursor
@@ -176,7 +176,7 @@
      * @return the text before the cursor position; the length of the
      * returned text might be less than <var>n</var>.
      */
-    public CharSequence getTextBeforeCursor(int n, int flags);
+    CharSequence getTextBeforeCursor(int n, int flags);
 
     /**
      * Get <var>n</var> characters of text after the current cursor
@@ -215,7 +215,7 @@
      * @return the text after the cursor position; the length of the
      * returned text might be less than <var>n</var>.
      */
-    public CharSequence getTextAfterCursor(int n, int flags);
+    CharSequence getTextAfterCursor(int n, int flags);
 
     /**
      * Gets the selected text, if any.
@@ -249,7 +249,7 @@
      * later, returns false when the target application does not implement
      * this method.
      */
-    public CharSequence getSelectedText(int flags);
+    CharSequence getSelectedText(int flags);
 
     /**
      * Retrieve the current capitalization mode in effect at the
@@ -279,7 +279,7 @@
      * @return the caps mode flags that are in effect at the current
      * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}.
      */
-    public int getCursorCapsMode(int reqModes);
+    int getCursorCapsMode(int reqModes);
 
     /**
      * Retrieve the current text in the input connection's editor, and
@@ -314,8 +314,7 @@
      * longer valid of the editor can't comply with the request for
      * some reason.
      */
-    public ExtractedText getExtractedText(ExtractedTextRequest request,
-            int flags);
+    ExtractedText getExtractedText(ExtractedTextRequest request, int flags);
 
     /**
      * Delete <var>beforeLength</var> characters of text before the
@@ -342,8 +341,8 @@
      * delete more characters than are in the editor, as that may have
      * ill effects on the application. Calling this method will cause
      * the editor to call
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * on your service after the batch input is over.</p>
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on your service after the batch input is over.</p>
      *
      * <p><strong>Editor authors:</strong> please be careful of race
      * conditions in implementing this call. An IME can make a change
@@ -369,7 +368,7 @@
      *        that range.
      * @return true on success, false if the input connection is no longer valid.
      */
-    public boolean deleteSurroundingText(int beforeLength, int afterLength);
+    boolean deleteSurroundingText(int beforeLength, int afterLength);
 
     /**
      * A variant of {@link #deleteSurroundingText(int, int)}. Major differences are:
@@ -397,7 +396,7 @@
      * @return true on success, false if the input connection is no longer valid.  Returns
      * {@code false} when the target application does not implement this method.
      */
-    public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
+    boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
 
     /**
      * Replace the currently composing text with the given text, and
@@ -416,8 +415,8 @@
      * <p>This is usually called by IMEs to add or remove or change
      * characters in the composing span. Calling this method will
      * cause the editor to call
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * on the current IME after the batch input is over.</p>
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.</p>
      *
      * <p><strong>Editor authors:</strong> please keep in mind the
      * text may be very similar or completely different than what was
@@ -455,7 +454,7 @@
      * @return true on success, false if the input connection is no longer
      * valid.
      */
-    public boolean setComposingText(CharSequence text, int newCursorPosition);
+    boolean setComposingText(CharSequence text, int newCursorPosition);
 
     /**
      * Mark a certain region of text as composing text. If there was a
@@ -474,8 +473,8 @@
      * <p>Since this does not change the contents of the text, editors should not call
      * {@link InputMethodManager#updateSelection(View, int, int, int, int)} and
      * IMEs should not receive
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}.
-     * </p>
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)}.</p>
      *
      * <p>This has no impact on the cursor/selection position. It may
      * result in the cursor being anywhere inside or outside the
@@ -488,7 +487,7 @@
      * valid. In {@link android.os.Build.VERSION_CODES#N} and later, false is returned when the
      * target application does not implement this method.
      */
-    public boolean setComposingRegion(int start, int end);
+    boolean setComposingRegion(int start, int end);
 
     /**
      * Have the text editor finish whatever composing text is
@@ -507,7 +506,7 @@
      * @return true on success, false if the input connection
      * is no longer valid.
      */
-    public boolean finishComposingText();
+    boolean finishComposingText();
 
     /**
      * Commit text to the text box and set the new cursor position.
@@ -522,8 +521,8 @@
      * then {@link #finishComposingText()}.</p>
      *
      * <p>Calling this method will cause the editor to call
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * on the current IME after the batch input is over.
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.
      * <strong>Editor authors</strong>, for this to happen you need to
      * make the changes known to the input method by calling
      * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
@@ -543,7 +542,7 @@
      * @return true on success, false if the input connection is no longer
      * valid.
      */
-    public boolean commitText(CharSequence text, int newCursorPosition);
+    boolean commitText(CharSequence text, int newCursorPosition);
 
     /**
      * Commit a completion the user has selected from the possible ones
@@ -569,8 +568,8 @@
      *
      * <p>Calling this method (with a valid {@link CompletionInfo} object)
      * will cause the editor to call
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * on the current IME after the batch input is over.
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.
      * <strong>Editor authors</strong>, for this to happen you need to
      * make the changes known to the input method by calling
      * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
@@ -581,15 +580,15 @@
      * @return true on success, false if the input connection is no longer
      * valid.
      */
-    public boolean commitCompletion(CompletionInfo text);
+    boolean commitCompletion(CompletionInfo text);
 
     /**
      * Commit a correction automatically performed on the raw user's input. A
      * typical example would be to correct typos using a dictionary.
      *
      * <p>Calling this method will cause the editor to call
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * on the current IME after the batch input is over.
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.
      * <strong>Editor authors</strong>, for this to happen you need to
      * make the changes known to the input method by calling
      * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
@@ -601,7 +600,7 @@
      * In {@link android.os.Build.VERSION_CODES#N} and later, returns false
      * when the target application does not implement this method.
      */
-    public boolean commitCorrection(CorrectionInfo correctionInfo);
+    boolean commitCorrection(CorrectionInfo correctionInfo);
 
     /**
      * Set the selection of the text editor. To set the cursor
@@ -609,8 +608,8 @@
      *
      * <p>Since this moves the cursor, calling this method will cause
      * the editor to call
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * on the current IME after the batch input is over.
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} on the current IME after the batch input is over.
      * <strong>Editor authors</strong>, for this to happen you need to
      * make the changes known to the input method by calling
      * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
@@ -628,7 +627,7 @@
      * @return true on success, false if the input connection is no longer
      * valid.
      */
-    public boolean setSelection(int start, int end);
+    boolean setSelection(int start, int end);
 
     /**
      * Have the editor perform an action it has said it can do.
@@ -642,7 +641,7 @@
      * @return true on success, false if the input connection is no longer
      * valid.
      */
-    public boolean performEditorAction(int editorAction);
+    boolean performEditorAction(int editorAction);
 
     /**
      * Perform a context menu action on the field. The given id may be one of:
@@ -652,7 +651,7 @@
      * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
      * or {@link android.R.id#switchInputMethod}
      */
-    public boolean performContextMenuAction(int id);
+    boolean performContextMenuAction(int id);
 
     /**
      * Tell the editor that you are starting a batch of editor
@@ -662,8 +661,8 @@
      *
      * <p><strong>IME authors:</strong> use this to avoid getting
      * calls to
-     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
-     * corresponding to intermediate state. Also, use this to avoid
+     * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+     * int, int)} corresponding to intermediate state. Also, use this to avoid
      * flickers that may arise from displaying intermediate state. Be
      * sure to call {@link #endBatchEdit} for each call to this, or
      * you may block updates in the editor.</p>
@@ -678,7 +677,7 @@
      * this method starts a batch edit, that means it will always return true
      * unless the input connection is no longer valid.
      */
-    public boolean beginBatchEdit();
+    boolean beginBatchEdit();
 
     /**
      * Tell the editor that you are done with a batch edit previously
@@ -696,7 +695,7 @@
      * the latest one (in other words, if the nesting count is > 0), false
      * otherwise or if the input connection is no longer valid.
      */
-    public boolean endBatchEdit();
+    boolean endBatchEdit();
 
     /**
      * Send a key event to the process that is currently attached
@@ -734,7 +733,7 @@
      * @see KeyCharacterMap#PREDICTIVE
      * @see KeyCharacterMap#ALPHA
      */
-    public boolean sendKeyEvent(KeyEvent event);
+    boolean sendKeyEvent(KeyEvent event);
 
     /**
      * Clear the given meta key pressed states in the given input
@@ -749,7 +748,7 @@
      * @return true on success, false if the input connection is no longer
      * valid.
      */
-    public boolean clearMetaKeyStates(int states);
+    boolean clearMetaKeyStates(int states);
 
     /**
      * Called back when the connected IME switches between fullscreen and normal modes.
@@ -766,7 +765,7 @@
      *         devices.
      * @see InputMethodManager#isFullscreenMode()
      */
-    public boolean reportFullscreenMode(boolean enabled);
+    boolean reportFullscreenMode(boolean enabled);
 
     /**
      * API to send private commands from an input method to its
@@ -786,7 +785,7 @@
      * associated editor understood it), false if the input connection is no longer
      * valid.
      */
-    public boolean performPrivateCommand(String action, Bundle data);
+    boolean performPrivateCommand(String action, Bundle data);
 
     /**
      * The editor is requested to call
@@ -794,7 +793,7 @@
      * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be
      * used together with {@link #CURSOR_UPDATE_MONITOR}.
      */
-    public static final int CURSOR_UPDATE_IMMEDIATE = 1 << 0;
+    int CURSOR_UPDATE_IMMEDIATE = 1 << 0;
 
     /**
      * The editor is requested to call
@@ -805,7 +804,7 @@
      * This flag can be used together with {@link #CURSOR_UPDATE_IMMEDIATE}.
      * </p>
      */
-    public static final int CURSOR_UPDATE_MONITOR = 1 << 1;
+    int CURSOR_UPDATE_MONITOR = 1 << 1;
 
     /**
      * Called by the input method to ask the editor for calling back
@@ -821,7 +820,7 @@
      * In {@link android.os.Build.VERSION_CODES#N} and later, returns {@code false} also when the
      * target application does not implement this method.
      */
-    public boolean requestCursorUpdates(int cursorUpdateMode);
+    boolean requestCursorUpdates(int cursorUpdateMode);
 
     /**
      * Called by the {@link InputMethodManager} to enable application developers to specify a
@@ -832,7 +831,7 @@
      *
      * @return {@code null} to use the default {@link Handler}.
      */
-    public Handler getHandler();
+    Handler getHandler();
 
     /**
      * Called by the system up to only once to notify that the system is about to invalidate
@@ -846,7 +845,7 @@
      *
      * <p>Note: This does nothing when called from input methods.</p>
      */
-    public void closeConnection();
+    void closeConnection();
 
     /**
      * When this flag is used, the editor will be able to request read access to the content URI
@@ -863,7 +862,7 @@
      * client is able to request a temporary read-only access even after the current IME is switched
      * to any other IME as long as the client keeps {@link InputContentInfo} object.</p>
      **/
-    public static int INPUT_CONTENT_GRANT_READ_URI_PERMISSION =
+    int INPUT_CONTENT_GRANT_READ_URI_PERMISSION =
             android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;  // 0x00000001
 
     /**
@@ -897,6 +896,6 @@
      * @return {@code true} if this request is accepted by the application, whether the request
      * is already handled or still being handled in background, {@code false} otherwise.
      */
-    public boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags,
+    boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags,
             @Nullable Bundle opts);
 }
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 317730c..f671e22 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -1,17 +1,17 @@
 /*
- * Copyright (C) 2007-2008 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
- * 
+ * Copyright (C) 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.
+ * 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.view.inputmethod;
@@ -74,6 +74,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public CharSequence getTextBeforeCursor(int n, int flags) {
         return mTarget.getTextBeforeCursor(n, flags);
     }
@@ -82,6 +83,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public CharSequence getTextAfterCursor(int n, int flags) {
         return mTarget.getTextAfterCursor(n, flags);
     }
@@ -90,6 +92,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public CharSequence getSelectedText(int flags) {
         return mTarget.getSelectedText(flags);
     }
@@ -98,6 +101,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public int getCursorCapsMode(int reqModes) {
         return mTarget.getCursorCapsMode(reqModes);
     }
@@ -106,6 +110,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
         return mTarget.getExtractedText(request, flags);
     }
@@ -114,6 +119,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
         return mTarget.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
     }
@@ -122,6 +128,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
         return mTarget.deleteSurroundingText(beforeLength, afterLength);
     }
@@ -130,6 +137,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean setComposingText(CharSequence text, int newCursorPosition) {
         return mTarget.setComposingText(text, newCursorPosition);
     }
@@ -138,6 +146,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean setComposingRegion(int start, int end) {
         return mTarget.setComposingRegion(start, end);
     }
@@ -146,6 +155,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean finishComposingText() {
         return mTarget.finishComposingText();
     }
@@ -154,6 +164,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean commitText(CharSequence text, int newCursorPosition) {
         return mTarget.commitText(text, newCursorPosition);
     }
@@ -162,6 +173,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean commitCompletion(CompletionInfo text) {
         return mTarget.commitCompletion(text);
     }
@@ -170,6 +182,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean commitCorrection(CorrectionInfo correctionInfo) {
         return mTarget.commitCorrection(correctionInfo);
     }
@@ -178,6 +191,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean setSelection(int start, int end) {
         return mTarget.setSelection(start, end);
     }
@@ -186,6 +200,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean performEditorAction(int editorAction) {
         return mTarget.performEditorAction(editorAction);
     }
@@ -194,6 +209,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean performContextMenuAction(int id) {
         return mTarget.performContextMenuAction(id);
     }
@@ -202,6 +218,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean beginBatchEdit() {
         return mTarget.beginBatchEdit();
     }
@@ -210,6 +227,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean endBatchEdit() {
         return mTarget.endBatchEdit();
     }
@@ -218,6 +236,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean sendKeyEvent(KeyEvent event) {
         return mTarget.sendKeyEvent(event);
     }
@@ -226,6 +245,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean clearMetaKeyStates(int states) {
         return mTarget.clearMetaKeyStates(states);
     }
@@ -234,6 +254,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean reportFullscreenMode(boolean enabled) {
         return mTarget.reportFullscreenMode(enabled);
     }
@@ -242,6 +263,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean performPrivateCommand(String action, Bundle data) {
         return mTarget.performPrivateCommand(action, data);
     }
@@ -250,6 +272,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean requestCursorUpdates(int cursorUpdateMode) {
         return mTarget.requestCursorUpdates(cursorUpdateMode);
     }
@@ -258,6 +281,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public Handler getHandler() {
         return mTarget.getHandler();
     }
@@ -266,6 +290,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public void closeConnection() {
         mTarget.closeConnection();
     }
@@ -274,6 +299,7 @@
      * {@inheritDoc}
      * @throws NullPointerException if the target is {@code null}.
      */
+    @Override
     public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
         return mTarget.commitContent(inputContentInfo, flags, opts);
     }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 80d7b6b7..7db5c32 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -337,20 +337,23 @@
     int mCursorCandEnd;
 
     /**
-     * Represents an invalid action notification sequence number. {@link InputMethodManagerService}
-     * always issues a positive integer for action notification sequence numbers. Thus -1 is
-     * guaranteed to be different from any valid sequence number.
+     * Represents an invalid action notification sequence number.
+     * {@link com.android.server.InputMethodManagerService} always issues a positive integer for
+     * action notification sequence numbers. Thus {@code -1} is guaranteed to be different from any
+     * valid sequence number.
      */
     private static final int NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER = -1;
     /**
-     * The next sequence number that is to be sent to {@link InputMethodManagerService} via
+     * The next sequence number that is to be sent to
+     * {@link com.android.server.InputMethodManagerService} via
      * {@link IInputMethodManager#notifyUserAction(int)} at once when a user action is observed.
      */
     private int mNextUserActionNotificationSequenceNumber =
             NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER;
 
     /**
-     * The last sequence number that is already sent to {@link InputMethodManagerService}.
+     * The last sequence number that is already sent to
+     * {@link com.android.server.InputMethodManagerService}.
      */
     private int mLastSentUserActionNotificationSequenceNumber =
             NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER;
@@ -1079,15 +1082,15 @@
     }
 
     /**
-     * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
-     * input window should only be hidden if it was not explicitly shown
+     * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)}
+     * to indicate that the soft input window should only be hidden if it was not explicitly shown
      * by the user.
      */
     public static final int HIDE_IMPLICIT_ONLY = 0x0001;
 
     /**
-     * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
-     * input window should normally be hidden, unless it was originally
+     * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestShowSelf(int)}
+     * to indicate that the soft input window should normally be hidden, unless it was originally
      * shown with {@link #SHOW_FORCED}.
      */
     public static final int HIDE_NOT_ALWAYS = 0x0002;
@@ -1255,12 +1258,7 @@
             // The view is running on a different thread than our own, so
             // we need to reschedule our work for over there.
             if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
-            vh.post(new Runnable() {
-                @Override
-                public void run() {
-                    startInputInner(startInputReason, null, 0, 0, 0);
-                }
-            });
+            vh.post(() -> startInputInner(startInputReason, null, 0, 0, 0));
             return false;
         }
 
@@ -1871,9 +1869,9 @@
      * @param flags Provides additional operating flags.  Currently may be
      * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
      * {@link #HIDE_NOT_ALWAYS} bit set.
-     * @deprecated Use {@link InputMethodService#hideSoftInputFromInputMethod(int)}
-     * instead. This method was intended for IME developers who should be accessing APIs through
-     * the service. APIs in this class are intended for app developers interacting with the IME.
+     * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was
+     * intended for IME developers who should be accessing APIs through the service. APIs in this
+     * class are intended for app developers interacting with the IME.
      */
     @Deprecated
     public void hideSoftInputFromInputMethod(IBinder token, int flags) {
@@ -1903,9 +1901,9 @@
      * @param flags Provides additional operating flags.  Currently may be
      * 0 or have the {@link #SHOW_IMPLICIT} or
      * {@link #SHOW_FORCED} bit set.
-     * @deprecated Use {@link InputMethodService#showSoftInputFromInputMethod(int)}
-     * instead. This method was intended for IME developers who should be accessing APIs through
-     * the service. APIs in this class are intended for app developers interacting with the IME.
+     * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was
+     * intended for IME developers who should be accessing APIs through the service. APIs in this
+     * class are intended for app developers interacting with the IME.
      */
     @Deprecated
     public void showSoftInputFromInputMethod(IBinder token, int flags) {
@@ -2429,8 +2427,8 @@
      * Allow the receiver of {@link InputContentInfo} to obtain a temporary read-only access
      * permission to the content.
      *
-     * <p>See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo, EditorInfo)}
-     * for details.</p>
+     * <p>See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo,
+     * InputConnection)} for details.</p>
      *
      * @param token Supplies the identifying token given to an input method when it was started,
      * which allows it to perform this operation on itself.
diff --git a/core/java/android/view/textclassifier/EntityConfidence.java b/core/java/android/view/textclassifier/EntityConfidence.java
index 19660d9..69a59a5 100644
--- a/core/java/android/view/textclassifier/EntityConfidence.java
+++ b/core/java/android/view/textclassifier/EntityConfidence.java
@@ -18,6 +18,8 @@
 
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.ArrayMap;
 
 import com.android.internal.util.Preconditions;
@@ -30,17 +32,16 @@
 /**
  * Helper object for setting and getting entity scores for classified text.
  *
- * @param <T> the entity type.
  * @hide
  */
-final class EntityConfidence<T> {
+final class EntityConfidence implements Parcelable {
 
-    private final ArrayMap<T, Float> mEntityConfidence = new ArrayMap<>();
-    private final ArrayList<T> mSortedEntities = new ArrayList<>();
+    private final ArrayMap<String, Float> mEntityConfidence = new ArrayMap<>();
+    private final ArrayList<String> mSortedEntities = new ArrayList<>();
 
     EntityConfidence() {}
 
-    EntityConfidence(@NonNull EntityConfidence<T> source) {
+    EntityConfidence(@NonNull EntityConfidence source) {
         Preconditions.checkNotNull(source);
         mEntityConfidence.putAll(source.mEntityConfidence);
         mSortedEntities.addAll(source.mSortedEntities);
@@ -54,24 +55,16 @@
      * @param source a map from entity to a confidence value in the range 0 (low confidence) to
      *               1 (high confidence).
      */
-    EntityConfidence(@NonNull Map<T, Float> source) {
+    EntityConfidence(@NonNull Map<String, Float> source) {
         Preconditions.checkNotNull(source);
 
         // Prune non-existent entities and clamp to 1.
         mEntityConfidence.ensureCapacity(source.size());
-        for (Map.Entry<T, Float> it : source.entrySet()) {
+        for (Map.Entry<String, Float> it : source.entrySet()) {
             if (it.getValue() <= 0) continue;
             mEntityConfidence.put(it.getKey(), Math.min(1, it.getValue()));
         }
-
-        // Create a list of entities sorted by decreasing confidence for getEntities().
-        mSortedEntities.ensureCapacity(mEntityConfidence.size());
-        mSortedEntities.addAll(mEntityConfidence.keySet());
-        mSortedEntities.sort((e1, e2) -> {
-            float score1 = mEntityConfidence.get(e1);
-            float score2 = mEntityConfidence.get(e2);
-            return Float.compare(score2, score1);
-        });
+        resetSortedEntitiesFromMap();
     }
 
     /**
@@ -79,7 +72,7 @@
      * high confidence to low confidence.
      */
     @NonNull
-    public List<T> getEntities() {
+    public List<String> getEntities() {
         return Collections.unmodifiableList(mSortedEntities);
     }
 
@@ -89,7 +82,7 @@
      * classified text.
      */
     @FloatRange(from = 0.0, to = 1.0)
-    public float getConfidenceScore(T entity) {
+    public float getConfidenceScore(String entity) {
         if (mEntityConfidence.containsKey(entity)) {
             return mEntityConfidence.get(entity);
         }
@@ -100,4 +93,51 @@
     public String toString() {
         return mEntityConfidence.toString();
     }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mEntityConfidence.size());
+        for (Map.Entry<String, Float> entry : mEntityConfidence.entrySet()) {
+            dest.writeString(entry.getKey());
+            dest.writeFloat(entry.getValue());
+        }
+    }
+
+    public static final Parcelable.Creator<EntityConfidence> CREATOR =
+            new Parcelable.Creator<EntityConfidence>() {
+                @Override
+                public EntityConfidence createFromParcel(Parcel in) {
+                    return new EntityConfidence(in);
+                }
+
+                @Override
+                public EntityConfidence[] newArray(int size) {
+                    return new EntityConfidence[size];
+                }
+            };
+
+    private EntityConfidence(Parcel in) {
+        final int numEntities = in.readInt();
+        mEntityConfidence.ensureCapacity(numEntities);
+        for (int i = 0; i < numEntities; ++i) {
+            mEntityConfidence.put(in.readString(), in.readFloat());
+        }
+        resetSortedEntitiesFromMap();
+    }
+
+    private void resetSortedEntitiesFromMap() {
+        mSortedEntities.clear();
+        mSortedEntities.ensureCapacity(mEntityConfidence.size());
+        mSortedEntities.addAll(mEntityConfidence.keySet());
+        mSortedEntities.sort((e1, e2) -> {
+            float score1 = mEntityConfidence.get(e1);
+            float score2 = mEntityConfidence.get(e2);
+            return Float.compare(score2, score1);
+        });
+    }
 }
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 7ffbf63..7089677 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -22,8 +22,13 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.ArrayMap;
 import android.view.View.OnClickListener;
 import android.view.textclassifier.TextClassifier.EntityType;
@@ -52,7 +57,7 @@
  *   Button button = new Button(context);
  *   button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null);
  *   button.setText(classification.getLabel());
- *   button.setOnClickListener(classification.getOnClickListener());
+ *   button.setOnClickListener(v -> context.startActivity(classification.getIntent()));
  * }</pre>
  *
  * <p>e.g. starting an action mode with menu items that can handle the classified text:
@@ -90,7 +95,6 @@
  *       ...
  *   });
  * }</pre>
- *
  */
 public final class TextClassification {
 
@@ -99,6 +103,10 @@
      */
     static final TextClassification EMPTY = new TextClassification.Builder().build();
 
+    // TODO(toki): investigate a way to derive this based on device properties.
+    private static final int MAX_PRIMARY_ICON_SIZE = 192;
+    private static final int MAX_SECONDARY_ICON_SIZE = 144;
+
     @NonNull private final String mText;
     @Nullable private final Drawable mPrimaryIcon;
     @Nullable private final String mPrimaryLabel;
@@ -107,8 +115,7 @@
     @NonNull private final List<Drawable> mSecondaryIcons;
     @NonNull private final List<String> mSecondaryLabels;
     @NonNull private final List<Intent> mSecondaryIntents;
-    @NonNull private final List<OnClickListener> mSecondaryOnClickListeners;
-    @NonNull private final EntityConfidence<String> mEntityConfidence;
+    @NonNull private final EntityConfidence mEntityConfidence;
     @NonNull private final String mSignature;
 
     private TextClassification(
@@ -120,12 +127,10 @@
             @NonNull List<Drawable> secondaryIcons,
             @NonNull List<String> secondaryLabels,
             @NonNull List<Intent> secondaryIntents,
-            @NonNull List<OnClickListener> secondaryOnClickListeners,
             @NonNull Map<String, Float> entityConfidence,
             @NonNull String signature) {
         Preconditions.checkArgument(secondaryLabels.size() == secondaryIntents.size());
         Preconditions.checkArgument(secondaryIcons.size() == secondaryIntents.size());
-        Preconditions.checkArgument(secondaryOnClickListeners.size() == secondaryIntents.size());
         mText = text;
         mPrimaryIcon = primaryIcon;
         mPrimaryLabel = primaryLabel;
@@ -134,8 +139,7 @@
         mSecondaryIcons = secondaryIcons;
         mSecondaryLabels = secondaryLabels;
         mSecondaryIntents = secondaryIntents;
-        mSecondaryOnClickListeners = secondaryOnClickListeners;
-        mEntityConfidence = new EntityConfidence<>(entityConfidence);
+        mEntityConfidence = new EntityConfidence(entityConfidence);
         mSignature = signature;
     }
 
@@ -186,7 +190,6 @@
      * @see #getSecondaryIntent(int)
      * @see #getSecondaryLabel(int)
      * @see #getSecondaryIcon(int)
-     * @see #getSecondaryOnClickListener(int)
      */
     @IntRange(from = 0)
     public int getSecondaryActionsCount() {
@@ -198,13 +201,10 @@
      * classified text.
      *
      * @param index Index of the action to get the icon for.
-     *
      * @throws IndexOutOfBoundsException if the specified index is out of range.
-     *
      * @see #getSecondaryActionsCount() for the number of actions available.
      * @see #getSecondaryIntent(int)
      * @see #getSecondaryLabel(int)
-     * @see #getSecondaryOnClickListener(int)
      * @see #getIcon()
      */
     @Nullable
@@ -228,13 +228,10 @@
      * the classified text.
      *
      * @param index Index of the action to get the label for.
-     *
      * @throws IndexOutOfBoundsException if the specified index is out of range.
-     *
      * @see #getSecondaryActionsCount()
      * @see #getSecondaryIntent(int)
      * @see #getSecondaryIcon(int)
-     * @see #getSecondaryOnClickListener(int)
      * @see #getLabel()
      */
     @Nullable
@@ -257,13 +254,10 @@
      * Returns one of the <i>secondary</i> intents that may be fired to act on the classified text.
      *
      * @param index Index of the action to get the intent for.
-     *
      * @throws IndexOutOfBoundsException if the specified index is out of range.
-     *
      * @see #getSecondaryActionsCount()
      * @see #getSecondaryLabel(int)
      * @see #getSecondaryIcon(int)
-     * @see #getSecondaryOnClickListener(int)
      * @see #getIntent()
      */
     @Nullable
@@ -282,29 +276,10 @@
     }
 
     /**
-     * Returns one of the <i>secondary</i> OnClickListeners that may be triggered to act on the
-     * classified text.
-     *
-     * @param index Index of the action to get the click listener for.
-     *
-     * @throws IndexOutOfBoundsException if the specified index is out of range.
-     *
-     * @see #getSecondaryActionsCount()
-     * @see #getSecondaryIntent(int)
-     * @see #getSecondaryLabel(int)
-     * @see #getSecondaryIcon(int)
-     * @see #getOnClickListener()
-     */
-    @Nullable
-    public OnClickListener getSecondaryOnClickListener(int index) {
-        return mSecondaryOnClickListeners.get(index);
-    }
-
-    /**
      * Returns the <i>primary</i> OnClickListener that may be triggered to act on the classified
-     * text.
-     *
-     * @see #getSecondaryOnClickListener(int)
+     * text. This field is not parcelable and will be null for all objects read from a parcel.
+     * Instead, call Context#startActivity(Intent) with the result of #getSecondaryIntent(int).
+     * Note that this may fail if the activity doesn't have permission to send the intent.
      */
     @Nullable
     public OnClickListener getOnClickListener() {
@@ -334,6 +309,42 @@
                 mSignature);
     }
 
+    /** Helper for parceling via #ParcelableWrapper. */
+    private void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mText);
+        final Bitmap primaryIconBitmap = drawableToBitmap(mPrimaryIcon, MAX_PRIMARY_ICON_SIZE);
+        dest.writeInt(primaryIconBitmap != null ? 1 : 0);
+        if (primaryIconBitmap != null) {
+            primaryIconBitmap.writeToParcel(dest, flags);
+        }
+        dest.writeString(mPrimaryLabel);
+        dest.writeInt(mPrimaryIntent != null ? 1 : 0);
+        if (mPrimaryIntent != null) {
+            mPrimaryIntent.writeToParcel(dest, flags);
+        }
+        // mPrimaryOnClickListener is not parcelable.
+        dest.writeTypedList(drawablesToBitmaps(mSecondaryIcons, MAX_SECONDARY_ICON_SIZE));
+        dest.writeStringList(mSecondaryLabels);
+        dest.writeTypedList(mSecondaryIntents);
+        mEntityConfidence.writeToParcel(dest, flags);
+        dest.writeString(mSignature);
+    }
+
+    /** Helper for unparceling via #ParcelableWrapper. */
+    private TextClassification(Parcel in) {
+        mText = in.readString();
+        mPrimaryIcon = in.readInt() == 0
+                ? null : new BitmapDrawable(null, Bitmap.CREATOR.createFromParcel(in));
+        mPrimaryLabel = in.readString();
+        mPrimaryIntent = in.readInt() == 0 ? null : Intent.CREATOR.createFromParcel(in);
+        mPrimaryOnClickListener = null;  // not parcelable
+        mSecondaryIcons = bitmapsToDrawables(in.createTypedArrayList(Bitmap.CREATOR));
+        mSecondaryLabels = in.createStringArrayList();
+        mSecondaryIntents = in.createTypedArrayList(Intent.CREATOR);
+        mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
+        mSignature = in.readString();
+    }
+
     /**
      * Creates an OnClickListener that starts an activity with the specified intent.
      *
@@ -349,6 +360,68 @@
     }
 
     /**
+     * Returns a Bitmap representation of the Drawable
+     *
+     * @param drawable The drawable to convert.
+     * @param maxDims The maximum edge length of the resulting bitmap (in pixels).
+     */
+    @Nullable
+    private static Bitmap drawableToBitmap(@Nullable Drawable drawable, int maxDims) {
+        if (drawable == null) {
+            return null;
+        }
+        final int actualWidth = Math.max(1, drawable.getIntrinsicWidth());
+        final int actualHeight = Math.max(1, drawable.getIntrinsicHeight());
+        final double scaleWidth = ((double) maxDims) / actualWidth;
+        final double scaleHeight = ((double) maxDims) / actualHeight;
+        final double scale = Math.min(1.0, Math.min(scaleWidth, scaleHeight));
+        final int width = (int) (actualWidth * scale);
+        final int height = (int) (actualHeight * scale);
+        if (drawable instanceof BitmapDrawable) {
+            final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+            if (actualWidth != width || actualHeight != height) {
+                return Bitmap.createScaledBitmap(
+                        bitmapDrawable.getBitmap(), width, height, /*filter=*/false);
+            } else {
+                return bitmapDrawable.getBitmap();
+            }
+        } else {
+            final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            final Canvas canvas = new Canvas(bitmap);
+            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+            drawable.draw(canvas);
+            return bitmap;
+        }
+    }
+
+    /**
+     * Returns a list of drawables converted to Bitmaps
+     *
+     * @param drawables The drawables to convert.
+     * @param maxDims The maximum edge length of the resulting bitmaps (in pixels).
+     */
+    private static List<Bitmap> drawablesToBitmaps(List<Drawable> drawables, int maxDims) {
+        final List<Bitmap> bitmaps = new ArrayList<>(drawables.size());
+        for (Drawable drawable : drawables) {
+            bitmaps.add(drawableToBitmap(drawable, maxDims));
+        }
+        return bitmaps;
+    }
+
+    /** Returns a list of drawable wrappers for a list of bitmaps. */
+    private static List<Drawable> bitmapsToDrawables(List<Bitmap> bitmaps) {
+        final List<Drawable> drawables = new ArrayList<>(bitmaps.size());
+        for (Bitmap bitmap : bitmaps) {
+            if (bitmap != null) {
+                drawables.add(new BitmapDrawable(null, bitmap));
+            } else {
+                drawables.add(null);
+            }
+        }
+        return drawables;
+    }
+
+    /**
      * Builder for building {@link TextClassification} objects.
      *
      * <p>e.g.
@@ -358,9 +431,9 @@
      *          .setText(classifiedText)
      *          .setEntityType(TextClassifier.TYPE_EMAIL, 0.9)
      *          .setEntityType(TextClassifier.TYPE_OTHER, 0.1)
-     *          .setPrimaryAction(intent, label, icon, onClickListener)
-     *          .addSecondaryAction(intent1, label1, icon1, onClickListener1)
-     *          .addSecondaryAction(intent2, label2, icon2, onClickListener2)
+     *          .setPrimaryAction(intent, label, icon)
+     *          .addSecondaryAction(intent1, label1, icon1)
+     *          .addSecondaryAction(intent2, label2, icon2)
      *          .build();
      * }</pre>
      */
@@ -370,7 +443,6 @@
         @NonNull private final List<Drawable> mSecondaryIcons = new ArrayList<>();
         @NonNull private final List<String> mSecondaryLabels = new ArrayList<>();
         @NonNull private final List<Intent> mSecondaryIntents = new ArrayList<>();
-        @NonNull private final List<OnClickListener> mSecondaryOnClickListeners = new ArrayList<>();
         @NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
         @Nullable Drawable mPrimaryIcon;
         @Nullable String mPrimaryLabel;
@@ -413,16 +485,14 @@
          * <p><stong>Note: </stong> If all input parameters are set to null, this method will be a
          * no-op.
          *
-         * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
+         * @see #setPrimaryAction(Intent, String, Drawable)
          */
         public Builder addSecondaryAction(
-                @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon,
-                @Nullable OnClickListener onClickListener) {
-            if (intent != null || label != null || icon != null || onClickListener != null) {
+                @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) {
+            if (intent != null || label != null || icon != null) {
                 mSecondaryIntents.add(intent);
                 mSecondaryLabels.add(label);
                 mSecondaryIcons.add(icon);
-                mSecondaryOnClickListeners.add(onClickListener);
             }
             return this;
         }
@@ -432,7 +502,6 @@
          */
         public Builder clearSecondaryActions() {
             mSecondaryIntents.clear();
-            mSecondaryOnClickListeners.clear();
             mSecondaryLabels.clear();
             mSecondaryIcons.clear();
             return this;
@@ -440,26 +509,23 @@
 
         /**
          * Sets the <i>primary</i> action that may be performed on the classified text. This is
-         * equivalent to calling {@code
-         * setIntent(intent).setLabel(label).setIcon(icon).setOnClickListener(onClickListener)}.
+         * equivalent to calling {@code setIntent(intent).setLabel(label).setIcon(icon)}.
          *
          * <p><strong>Note: </strong>If all input parameters are null, there will be no
          * <i>primary</i> action but there may still be <i>secondary</i> actions.
          *
-         * @see #addSecondaryAction(Intent, String, Drawable, OnClickListener)
+         * @see #addSecondaryAction(Intent, String, Drawable)
          */
         public Builder setPrimaryAction(
-                @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon,
-                @Nullable OnClickListener onClickListener) {
-            return setIntent(intent).setLabel(label).setIcon(icon)
-                    .setOnClickListener(onClickListener);
+                @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) {
+            return setIntent(intent).setLabel(label).setIcon(icon);
         }
 
         /**
          * Sets the icon for the <i>primary</i> action that may be rendered on a widget used to act
          * on the classified text.
          *
-         * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
+         * @see #setPrimaryAction(Intent, String, Drawable)
          */
         public Builder setIcon(@Nullable Drawable icon) {
             mPrimaryIcon = icon;
@@ -470,7 +536,7 @@
          * Sets the label for the <i>primary</i> action that may be rendered on a widget used to
          * act on the classified text.
          *
-         * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
+         * @see #setPrimaryAction(Intent, String, Drawable)
          */
         public Builder setLabel(@Nullable String label) {
             mPrimaryLabel = label;
@@ -481,7 +547,7 @@
          * Sets the intent for the <i>primary</i> action that may be fired to act on the classified
          * text.
          *
-         * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
+         * @see #setPrimaryAction(Intent, String, Drawable)
          */
         public Builder setIntent(@Nullable Intent intent) {
             mPrimaryIntent = intent;
@@ -490,9 +556,8 @@
 
         /**
          * Sets the OnClickListener for the <i>primary</i> action that may be triggered to act on
-         * the classified text.
-         *
-         * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
+         * the classified text. This field is not parcelable and will always be null when the
+         * object is read from a parcel.
          */
         public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
             mPrimaryOnClickListener = onClickListener;
@@ -515,10 +580,8 @@
         public TextClassification build() {
             return new TextClassification(
                     mText,
-                    mPrimaryIcon, mPrimaryLabel,
-                    mPrimaryIntent, mPrimaryOnClickListener,
-                    mSecondaryIcons, mSecondaryLabels,
-                    mSecondaryIntents, mSecondaryOnClickListeners,
+                    mPrimaryIcon, mPrimaryLabel, mPrimaryIntent, mPrimaryOnClickListener,
+                    mSecondaryIcons, mSecondaryLabels, mSecondaryIntents,
                     mEntityConfidence, mSignature);
         }
     }
@@ -526,9 +589,11 @@
     /**
      * Optional input parameters for generating TextClassification.
      */
-    public static final class Options {
+    public static final class Options implements Parcelable {
 
-        private LocaleList mDefaultLocales;
+        private @Nullable LocaleList mDefaultLocales;
+
+        public Options() {}
 
         /**
          * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
@@ -548,5 +613,80 @@
         public LocaleList getDefaultLocales() {
             return mDefaultLocales;
         }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mDefaultLocales != null ? 1 : 0);
+            if (mDefaultLocales != null) {
+                mDefaultLocales.writeToParcel(dest, flags);
+            }
+        }
+
+        public static final Parcelable.Creator<Options> CREATOR =
+                new Parcelable.Creator<Options>() {
+                    @Override
+                    public Options createFromParcel(Parcel in) {
+                        return new Options(in);
+                    }
+
+                    @Override
+                    public Options[] newArray(int size) {
+                        return new Options[size];
+                    }
+                };
+
+        private Options(Parcel in) {
+            if (in.readInt() > 0) {
+                mDefaultLocales = LocaleList.CREATOR.createFromParcel(in);
+            }
+        }
+    }
+
+    /**
+     * Parcelable wrapper for TextClassification objects.
+     * @hide
+     */
+    public static final class ParcelableWrapper implements Parcelable {
+
+        @NonNull private TextClassification mTextClassification;
+
+        public ParcelableWrapper(@NonNull TextClassification textClassification) {
+            Preconditions.checkNotNull(textClassification);
+            mTextClassification = textClassification;
+        }
+
+        @NonNull
+        public TextClassification getTextClassification() {
+            return mTextClassification;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            mTextClassification.writeToParcel(dest, flags);
+        }
+
+        public static final Parcelable.Creator<ParcelableWrapper> CREATOR =
+                new Parcelable.Creator<ParcelableWrapper>() {
+                    @Override
+                    public ParcelableWrapper createFromParcel(Parcel in) {
+                        return new ParcelableWrapper(new TextClassification(in));
+                    }
+
+                    @Override
+                    public ParcelableWrapper[] newArray(int size) {
+                        return new ParcelableWrapper[size];
+                    }
+                };
+
     }
 }
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index ed60430..b602095 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -23,6 +23,8 @@
 import android.annotation.StringDef;
 import android.annotation.WorkerThread;
 import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.ArraySet;
 
 import com.android.internal.util.Preconditions;
@@ -305,7 +307,7 @@
      *
      * Configs are initially based on a predefined preset, and can be modified from there.
      */
-    final class EntityConfig {
+    final class EntityConfig implements Parcelable {
         private final @TextClassifier.EntityPreset int mEntityPreset;
         private final Collection<String> mExcludedEntityTypes;
         private final Collection<String> mIncludedEntityTypes;
@@ -355,6 +357,37 @@
             }
             return Collections.unmodifiableList(entities);
         }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mEntityPreset);
+            dest.writeStringList(new ArrayList<>(mExcludedEntityTypes));
+            dest.writeStringList(new ArrayList<>(mIncludedEntityTypes));
+        }
+
+        public static final Parcelable.Creator<EntityConfig> CREATOR =
+                new Parcelable.Creator<EntityConfig>() {
+                    @Override
+                    public EntityConfig createFromParcel(Parcel in) {
+                        return new EntityConfig(in);
+                    }
+
+                    @Override
+                    public EntityConfig[] newArray(int size) {
+                        return new EntityConfig[size];
+                    }
+                };
+
+        private EntityConfig(Parcel in) {
+            mEntityPreset = in.readInt();
+            mExcludedEntityTypes = new ArraySet<>(in.createStringArrayList());
+            mIncludedEntityTypes = new ArraySet<>(in.createStringArrayList());
+        }
     }
 
     /**
diff --git a/core/java/android/view/textclassifier/TextClassifierConstants.java b/core/java/android/view/textclassifier/TextClassifierConstants.java
index 51e6168..00695b7 100644
--- a/core/java/android/view/textclassifier/TextClassifierConstants.java
+++ b/core/java/android/view/textclassifier/TextClassifierConstants.java
@@ -45,19 +45,24 @@
             "smart_selection_dark_launch";
     private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT =
             "smart_selection_enabled_for_edit_text";
+    private static final String SMART_LINKIFY_ENABLED =
+            "smart_linkify_enabled";
 
     private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false;
     private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true;
+    private static final boolean SMART_LINKIFY_ENABLED_DEFAULT = true;
 
     /** Default settings. */
     static final TextClassifierConstants DEFAULT = new TextClassifierConstants();
 
     private final boolean mDarkLaunch;
     private final boolean mSuggestSelectionEnabledForEditableText;
+    private final boolean mSmartLinkifyEnabled;
 
     private TextClassifierConstants() {
         mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT;
         mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT;
+        mSmartLinkifyEnabled = SMART_LINKIFY_ENABLED_DEFAULT;
     }
 
     private TextClassifierConstants(@Nullable String settings) {
@@ -74,6 +79,9 @@
         mSuggestSelectionEnabledForEditableText = parser.getBoolean(
                 SMART_SELECTION_ENABLED_FOR_EDIT_TEXT,
                 SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT);
+        mSmartLinkifyEnabled = parser.getBoolean(
+                SMART_LINKIFY_ENABLED,
+                SMART_LINKIFY_ENABLED_DEFAULT);
     }
 
     static TextClassifierConstants loadFromString(String settings) {
@@ -87,4 +95,8 @@
     public boolean isSuggestSelectionEnabledForEditableText() {
         return mSuggestSelectionEnabledForEditableText;
     }
+
+    public boolean isSmartLinkifyEnabled() {
+        return mSmartLinkifyEnabled;
+    }
 }
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index aea3cb0..7db0e76 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -32,7 +32,6 @@
 import android.provider.Settings;
 import android.text.util.Linkify;
 import android.util.Patterns;
-import android.view.View.OnClickListener;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
@@ -187,6 +186,11 @@
         Utils.validateInput(text);
         final String textString = text.toString();
         final TextLinks.Builder builder = new TextLinks.Builder(textString);
+
+        if (!getSettings().isSmartLinkifyEnabled()) {
+            return builder.build();
+        }
+
         try {
             final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
             final Collection<String> entitiesToIdentify =
@@ -457,12 +461,10 @@
                     }
                 }
                 final String labelString = (label != null) ? label.toString() : null;
-                final OnClickListener onClickListener =
-                        TextClassification.createStartActivityOnClickListener(mContext, intent);
                 if (i == 0) {
-                    builder.setPrimaryAction(intent, labelString, icon, onClickListener);
+                    builder.setPrimaryAction(intent, labelString, icon);
                 } else {
-                    builder.addSecondaryAction(intent, labelString, icon, onClickListener);
+                    builder.addSecondaryAction(intent, labelString, icon);
                 }
             }
         }
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index 6c587cf..ba854e0 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -20,6 +20,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.text.SpannableString;
 import android.text.style.ClickableSpan;
 import android.view.View;
@@ -38,7 +40,7 @@
  * A collection of links, representing subsequences of text and the entity types (phone number,
  * address, url, etc) they may be.
  */
-public final class TextLinks {
+public final class TextLinks implements Parcelable {
     private final String mFullText;
     private final List<TextLink> mLinks;
 
@@ -83,11 +85,40 @@
         return true;
     }
 
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mFullText);
+        dest.writeTypedList(mLinks);
+    }
+
+    public static final Parcelable.Creator<TextLinks> CREATOR =
+            new Parcelable.Creator<TextLinks>() {
+                @Override
+                public TextLinks createFromParcel(Parcel in) {
+                    return new TextLinks(in);
+                }
+
+                @Override
+                public TextLinks[] newArray(int size) {
+                    return new TextLinks[size];
+                }
+            };
+
+    private TextLinks(Parcel in) {
+        mFullText = in.readString();
+        mLinks = in.createTypedArrayList(TextLink.CREATOR);
+    }
+
     /**
      * A link, identifying a substring of text and possible entity types for it.
      */
-    public static final class TextLink {
-        private final EntityConfidence<String> mEntityScores;
+    public static final class TextLink implements Parcelable {
+        private final EntityConfidence mEntityScores;
         private final String mOriginalText;
         private final int mStart;
         private final int mEnd;
@@ -105,7 +136,7 @@
             mOriginalText = originalText;
             mStart = start;
             mEnd = end;
-            mEntityScores = new EntityConfidence<>(entityScores);
+            mEntityScores = new EntityConfidence(entityScores);
         }
 
         /**
@@ -153,16 +184,51 @@
                 @TextClassifier.EntityType String entityType) {
             return mEntityScores.getConfidenceScore(entityType);
         }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            mEntityScores.writeToParcel(dest, flags);
+            dest.writeString(mOriginalText);
+            dest.writeInt(mStart);
+            dest.writeInt(mEnd);
+        }
+
+        public static final Parcelable.Creator<TextLink> CREATOR =
+                new Parcelable.Creator<TextLink>() {
+                    @Override
+                    public TextLink createFromParcel(Parcel in) {
+                        return new TextLink(in);
+                    }
+
+                    @Override
+                    public TextLink[] newArray(int size) {
+                        return new TextLink[size];
+                    }
+                };
+
+        private TextLink(Parcel in) {
+            mEntityScores = EntityConfidence.CREATOR.createFromParcel(in);
+            mOriginalText = in.readString();
+            mStart = in.readInt();
+            mEnd = in.readInt();
+        }
     }
 
     /**
      * Optional input parameters for generating TextLinks.
      */
-    public static final class Options {
+    public static final class Options implements Parcelable {
 
         private LocaleList mDefaultLocales;
         private TextClassifier.EntityConfig mEntityConfig;
 
+        public Options() {}
+
         /**
          * @param defaultLocales ordered list of locale preferences that may be used to
          *                       disambiguate the provided text. If no locale preferences exist,
@@ -201,6 +267,45 @@
         public TextClassifier.EntityConfig getEntityConfig() {
             return mEntityConfig;
         }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mDefaultLocales != null ? 1 : 0);
+            if (mDefaultLocales != null) {
+                mDefaultLocales.writeToParcel(dest, flags);
+            }
+            dest.writeInt(mEntityConfig != null ? 1 : 0);
+            if (mEntityConfig != null) {
+                mEntityConfig.writeToParcel(dest, flags);
+            }
+        }
+
+        public static final Parcelable.Creator<Options> CREATOR =
+                new Parcelable.Creator<Options>() {
+                    @Override
+                    public Options createFromParcel(Parcel in) {
+                        return new Options(in);
+                    }
+
+                    @Override
+                    public Options[] newArray(int size) {
+                        return new Options[size];
+                    }
+                };
+
+        private Options(Parcel in) {
+            if (in.readInt() > 0) {
+                mDefaultLocales = LocaleList.CREATOR.createFromParcel(in);
+            }
+            if (in.readInt() > 0) {
+                mEntityConfig = TextClassifier.EntityConfig.CREATOR.createFromParcel(in);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index 25e9e7e..774d42d 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -21,6 +21,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.ArrayMap;
 import android.view.textclassifier.TextClassifier.EntityType;
 
@@ -36,7 +38,7 @@
 
     private final int mStartIndex;
     private final int mEndIndex;
-    @NonNull private final EntityConfidence<String> mEntityConfidence;
+    @NonNull private final EntityConfidence mEntityConfidence;
     @NonNull private final String mSignature;
 
     private TextSelection(
@@ -44,7 +46,7 @@
             @NonNull String signature) {
         mStartIndex = startIndex;
         mEndIndex = endIndex;
-        mEntityConfidence = new EntityConfidence<>(entityConfidence);
+        mEntityConfidence = new EntityConfidence(entityConfidence);
         mSignature = signature;
     }
 
@@ -110,6 +112,22 @@
                 mStartIndex, mEndIndex, mEntityConfidence, mSignature);
     }
 
+    /** Helper for parceling via #ParcelableWrapper. */
+    private void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mStartIndex);
+        dest.writeInt(mEndIndex);
+        mEntityConfidence.writeToParcel(dest, flags);
+        dest.writeString(mSignature);
+    }
+
+    /** Helper for unparceling via #ParcelableWrapper. */
+    private TextSelection(Parcel in) {
+        mStartIndex = in.readInt();
+        mEndIndex = in.readInt();
+        mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
+        mSignature = in.readString();
+    }
+
     /**
      * Builder used to build {@link TextSelection} objects.
      */
@@ -170,11 +188,13 @@
     /**
      * Optional input parameters for generating TextSelection.
      */
-    public static final class Options {
+    public static final class Options implements Parcelable {
 
-        private LocaleList mDefaultLocales;
+        private @Nullable LocaleList mDefaultLocales;
         private boolean mDarkLaunchAllowed;
 
+        public Options() {}
+
         /**
          * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
          *      the provided text. If no locale preferences exist, set this to null or an empty
@@ -216,5 +236,82 @@
         public boolean isDarkLaunchAllowed() {
             return mDarkLaunchAllowed;
         }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mDefaultLocales != null ? 1 : 0);
+            if (mDefaultLocales != null) {
+                mDefaultLocales.writeToParcel(dest, flags);
+            }
+            dest.writeInt(mDarkLaunchAllowed ? 1 : 0);
+        }
+
+        public static final Parcelable.Creator<Options> CREATOR =
+                new Parcelable.Creator<Options>() {
+                    @Override
+                    public Options createFromParcel(Parcel in) {
+                        return new Options(in);
+                    }
+
+                    @Override
+                    public Options[] newArray(int size) {
+                        return new Options[size];
+                    }
+                };
+
+        private Options(Parcel in) {
+            if (in.readInt() > 0) {
+                mDefaultLocales = LocaleList.CREATOR.createFromParcel(in);
+            }
+            mDarkLaunchAllowed = in.readInt() != 0;
+        }
+    }
+
+    /**
+     * Parcelable wrapper for TextSelection objects.
+     * @hide
+     */
+    public static final class ParcelableWrapper implements Parcelable {
+
+        @NonNull private TextSelection mTextSelection;
+
+        public ParcelableWrapper(@NonNull TextSelection textSelection) {
+            Preconditions.checkNotNull(textSelection);
+            mTextSelection = textSelection;
+        }
+
+        @NonNull
+        public TextSelection getTextSelection() {
+            return mTextSelection;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            mTextSelection.writeToParcel(dest, flags);
+        }
+
+        public static final Parcelable.Creator<ParcelableWrapper> CREATOR =
+                new Parcelable.Creator<ParcelableWrapper>() {
+                    @Override
+                    public ParcelableWrapper createFromParcel(Parcel in) {
+                        return new ParcelableWrapper(new TextSelection(in));
+                    }
+
+                    @Override
+                    public ParcelableWrapper[] newArray(int size) {
+                        return new ParcelableWrapper[size];
+                    }
+                };
+
     }
 }
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index b3522ec..e9fe481 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -27,7 +27,6 @@
 import android.content.pm.Signature;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.StrictMode;
 import android.os.Trace;
 import android.util.AndroidRuntimeException;
 import android.util.ArraySet;
@@ -251,7 +250,6 @@
                         "WebView.disableWebView() was called: WebView is disabled");
             }
 
-            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
             try {
                 Class<WebViewFactoryProvider> providerClass = getProviderClass();
@@ -279,7 +277,6 @@
                 }
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
-                StrictMode.setThreadPolicy(oldPolicy);
             }
         }
     }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index e0c897d..6bee58f 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -6849,7 +6849,7 @@
             // detached and we do not allow detached views to fire accessibility
             // events. So we are announcing that the subtree changed giving a chance
             // to clients holding on to a view in this subtree to refresh it.
-            notifyViewAccessibilityStateChangedIfNeeded(
+            notifyAccessibilityStateChanged(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
 
             // Don't scrap views that have transient state.
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index 6c19256..08374cb 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -1093,7 +1093,7 @@
             checkSelectionChanged();
         }
 
-        notifySubtreeAccessibilityStateChangedIfNeeded();
+        notifyAccessibilitySubtreeChanged();
     }
 
     /**
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 92bfd56..af01a3e 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -132,7 +132,7 @@
         if (mChecked != checked) {
             mChecked = checked;
             refreshDrawableState();
-            notifyViewAccessibilityStateChangedIfNeeded(
+            notifyAccessibilityStateChanged(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
         }
     }
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 0762b15..e57f153 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -158,7 +158,7 @@
             mCheckedFromResource = false;
             mChecked = checked;
             refreshDrawableState();
-            notifyViewAccessibilityStateChangedIfNeeded(
+            notifyAccessibilityStateChanged(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
 
             // Avoid infinite recursions if setChecked() is called from a listener
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index d00aa55..247c806 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2095,14 +2095,7 @@
         if (!(mTextView.getText() instanceof Spannable)) {
             return;
         }
-        Spannable text = (Spannable) mTextView.getText();
         stopTextActionMode();
-        if (mTextView.isTextSelectable()) {
-            Selection.setSelection((Spannable) text, link.getStart(), link.getEnd());
-        } else {
-            //TODO: Nonselectable text
-        }
-
         getSelectionActionModeHelper().startLinkActionModeAsync(link);
     }
 
@@ -2179,7 +2172,8 @@
             return false;
         }
 
-        if (!checkField() || !mTextView.hasSelection()) {
+        if (actionMode != TextActionMode.TEXT_LINK
+                && (!checkField() || !mTextView.hasSelection())) {
             return false;
         }
 
@@ -4012,7 +4006,6 @@
             if (isValidAssistMenuItem(
                     textClassification.getIcon(),
                     textClassification.getLabel(),
-                    textClassification.getOnClickListener(),
                     textClassification.getIntent())) {
                 final MenuItem item = menu.add(
                         TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST,
@@ -4020,14 +4013,15 @@
                         .setIcon(textClassification.getIcon())
                         .setIntent(textClassification.getIntent());
                 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
-                mAssistClickHandlers.put(item, textClassification.getOnClickListener());
+                mAssistClickHandlers.put(
+                        item, TextClassification.createStartActivityOnClickListener(
+                                mTextView.getContext(), textClassification.getIntent()));
             }
             final int count = textClassification.getSecondaryActionsCount();
             for (int i = 0; i < count; i++) {
                 if (!isValidAssistMenuItem(
                         textClassification.getSecondaryIcon(i),
                         textClassification.getSecondaryLabel(i),
-                        textClassification.getSecondaryOnClickListener(i),
                         textClassification.getSecondaryIntent(i))) {
                     continue;
                 }
@@ -4038,7 +4032,9 @@
                         .setIcon(textClassification.getSecondaryIcon(i))
                         .setIntent(textClassification.getSecondaryIntent(i));
                 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-                mAssistClickHandlers.put(item, textClassification.getSecondaryOnClickListener(i));
+                mAssistClickHandlers.put(item,
+                        TextClassification.createStartActivityOnClickListener(
+                                mTextView.getContext(), textClassification.getSecondaryIntent(i)));
             }
         }
 
@@ -4054,10 +4050,9 @@
             }
         }
 
-        private boolean isValidAssistMenuItem(
-                Drawable icon, CharSequence label, OnClickListener onClick, Intent intent) {
+        private boolean isValidAssistMenuItem(Drawable icon, CharSequence label, Intent intent) {
             final boolean hasUi = icon != null || !TextUtils.isEmpty(label);
-            final boolean hasAction = onClick != null || isSupportedIntent(intent);
+            final boolean hasAction = isSupportedIntent(intent);
             return hasUi && hasAction;
         }
 
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 2c6466c..3bfa520 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -235,10 +235,13 @@
             @Editor.TextActionMode int actionMode, @Nullable SelectionResult result) {
         final CharSequence text = getText(mTextView);
         if (result != null && text instanceof Spannable
-                && (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
+                && (mTextView.isTextSelectable()
+                    || mTextView.isTextEditable()
+                    || actionMode == Editor.TextActionMode.TEXT_LINK)) {
             // Do not change the selection if TextClassifier should be dark launched.
             if (!mTextView.getTextClassifier().getSettings().isDarkLaunch()) {
                 Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
+                mTextView.invalidate();
             }
             mTextClassification = result.mClassification;
         } else {
@@ -250,8 +253,17 @@
                     && (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
                 controller.show();
             }
-            if (result != null && actionMode == Editor.TextActionMode.SELECTION) {
-                mSelectionTracker.onSmartSelection(result);
+            if (result != null) {
+                switch (actionMode) {
+                    case Editor.TextActionMode.SELECTION:
+                        mSelectionTracker.onSmartSelection(result);
+                        break;
+                    case Editor.TextActionMode.TEXT_LINK:
+                        mSelectionTracker.onLinkSelected(result);
+                        break;
+                    default:
+                        break;
+                }
             }
         }
         mEditor.setRestartActionModeOnNextRefresh(false);
@@ -486,12 +498,24 @@
          * Called when selection action mode is started and the results come from a classifier.
          */
         public void onSmartSelection(SelectionResult result) {
+            onClassifiedSelection(result);
+            mLogger.logSelectionModified(
+                    result.mStart, result.mEnd, result.mClassification, result.mSelection);
+        }
+
+        /**
+         * Called when link action mode is started and the classification comes from a classifier.
+         */
+        public void onLinkSelected(SelectionResult result) {
+            onClassifiedSelection(result);
+            // TODO: log (b/70246800)
+        }
+
+        private void onClassifiedSelection(SelectionResult result) {
             if (isSelectionStarted()) {
                 mSelectionStart = result.mStart;
                 mSelectionEnd = result.mEnd;
                 mAllowReset = mSelectionStart != mOriginalStart || mSelectionEnd != mOriginalEnd;
-                mLogger.logSelectionModified(
-                        result.mStart, result.mEnd, result.mClassification, result.mSelection);
             }
         }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index cae2d7d9..8c4e422 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -77,8 +77,8 @@
 import android.text.InputFilter;
 import android.text.InputType;
 import android.text.Layout;
+import android.text.MeasuredText;
 import android.text.ParcelableSpan;
-import android.text.PremeasuredText;
 import android.text.Selection;
 import android.text.SpanWatcher;
 import android.text.Spannable;
@@ -2365,7 +2365,7 @@
         setText(mText);
 
         if (hasPasswordTransformationMethod()) {
-            notifyViewAccessibilityStateChangedIfNeeded(
+            notifyAccessibilityStateChanged(
                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
         }
 
@@ -5393,7 +5393,7 @@
             if (imm != null) imm.restartInput(this);
         } else if (type == BufferType.SPANNABLE || mMovement != null) {
             text = mSpannableFactory.newSpannable(text);
-        } else if (!(text instanceof PremeasuredText || text instanceof CharWrapper)) {
+        } else if (!(text instanceof MeasuredText || text instanceof CharWrapper)) {
             text = TextUtils.stringOrSpannedString(text);
         }
 
@@ -5476,7 +5476,7 @@
         sendOnTextChanged(text, 0, oldlen, textLength);
         onTextChanged(text, 0, oldlen, textLength);
 
-        notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
+        notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
 
         if (needEditableForNotification) {
             sendAfterTextChanged((Editable) text);
@@ -6210,7 +6210,7 @@
     public void setError(CharSequence error, Drawable icon) {
         createEditorIfNeeded();
         mEditor.setError(error, icon);
-        notifyViewAccessibilityStateChangedIfNeeded(
+        notifyAccessibilityStateChanged(
                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
@@ -11011,6 +11011,12 @@
                 return true;
 
             case ID_COPY:
+                // For link action mode in a non-selectable/non-focusable TextView,
+                // make sure that we set the appropriate min/max.
+                final int selStart = getSelectionStart();
+                final int selEnd = getSelectionEnd();
+                min = Math.max(0, Math.min(selStart, selEnd));
+                max = Math.max(0, Math.max(selStart, selEnd));
                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
                 if (setPrimaryClip(copyData)) {
                     stopTextActionMode();
@@ -11233,11 +11239,9 @@
      */
     public boolean requestActionMode(@NonNull TextLinks.TextLink link) {
         Preconditions.checkNotNull(link);
-        if (mEditor != null) {
-            mEditor.startLinkActionModeAsync(link);
-            return true;
-        }
-        return false;
+        createEditorIfNeeded();
+        mEditor.startLinkActionModeAsync(link);
+        return true;
     }
     /**
      * @hide
diff --git a/core/java/com/android/internal/app/HarmfulAppWarningActivity.java b/core/java/com/android/internal/app/HarmfulAppWarningActivity.java
new file mode 100644
index 0000000..042da36
--- /dev/null
+++ b/core/java/com/android/internal/app/HarmfulAppWarningActivity.java
@@ -0,0 +1,99 @@
+/*
+ * 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
+ */
+
+package com.android.internal.app;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.util.Log;
+import com.android.internal.R;
+
+/**
+ * This dialog is shown to the user before an activity in a harmful app is launched.
+ *
+ * See {@code PackageManager.setHarmfulAppInfo} for more info.
+ */
+public class HarmfulAppWarningActivity extends AlertActivity implements
+        DialogInterface.OnClickListener {
+    private static final String TAG = "HarmfulAppWarningActivity";
+
+    private static final String EXTRA_HARMFUL_APP_WARNING = "harmful_app_warning";
+
+    private String mPackageName;
+    private String mHarmfulAppWarning;
+    private IntentSender mTarget;
+
+    // [b/63909431] STOPSHIP replace placeholder UI with final Harmful App Warning UI
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+        mTarget = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+        mHarmfulAppWarning = intent.getStringExtra(EXTRA_HARMFUL_APP_WARNING);
+
+        if (mPackageName == null || mTarget == null || mHarmfulAppWarning == null) {
+            Log.wtf(TAG, "Invalid intent: " + intent.toString());
+            finish();
+        }
+
+        AlertController.AlertParams p = mAlertParams;
+        p.mTitle = getString(R.string.harmful_app_warning_title);
+        p.mMessage = mHarmfulAppWarning;
+        p.mPositiveButtonText = getString(R.string.harmful_app_warning_launch_anyway);
+        p.mPositiveButtonListener = this;
+        p.mNegativeButtonText = getString(R.string.harmful_app_warning_uninstall);
+        p.mNegativeButtonListener = this;
+
+        mAlert.installContent(mAlertParams);
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        switch (which) {
+            case DialogInterface.BUTTON_POSITIVE:
+                getPackageManager().setHarmfulAppWarning(mPackageName, null);
+
+                IntentSender target = getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
+                try {
+                    startIntentSenderForResult(target, -1, null, 0, 0, 0);
+                } catch (IntentSender.SendIntentException e) {
+                    // ignore..
+                }
+                finish();
+                break;
+            case DialogInterface.BUTTON_NEGATIVE:
+                getPackageManager().deletePackage(mPackageName, null, 0);
+                finish();
+                break;
+        }
+    }
+
+    public static Intent createHarmfulAppWarningIntent(Context context, String targetPackageName,
+            IntentSender target, CharSequence harmfulAppWarning) {
+        Intent intent = new Intent();
+        intent.setClass(context, HarmfulAppWarningActivity.class);
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
+        intent.putExtra(Intent.EXTRA_INTENT, target);
+        intent.putExtra(EXTRA_HARMFUL_APP_WARNING, harmfulAppWarning);
+        return intent;
+    }
+}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 5d4bccc..388180d 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -123,8 +123,6 @@
     void noteWifiScanStoppedFromSource(in WorkSource ws);
     void noteWifiBatchedScanStartedFromSource(in WorkSource ws, int csph);
     void noteWifiBatchedScanStoppedFromSource(in WorkSource ws);
-    void noteWifiMulticastEnabledFromSource(in WorkSource ws);
-    void noteWifiMulticastDisabledFromSource(in WorkSource ws);
     void noteWifiRadioPowerState(int powerState, long timestampNs, int uid);
     void noteNetworkInterfaceType(String iface, int type);
     void noteNetworkStatsEnabled();
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 2eadaf3..902c8c1 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -111,7 +111,7 @@
     @Override
     public void onClick(DialogInterface dialog, int which) {
         if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE && which == DialogInterface.BUTTON_POSITIVE) {
-            UserManager.get(this).trySetQuietModeEnabled(false, UserHandle.of(mUserId), mTarget);
+            UserManager.get(this).requestQuietModeEnabled(false, UserHandle.of(mUserId), mTarget);
         }
     }
 
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index b8ff9e4..439e5df 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -4062,7 +4062,8 @@
 
     public void noteWakupAlarmLocked(String packageName, int uid, WorkSource workSource,
             String tag) {
-
+        final int[] uids = new int[1];
+        final String[] tags = new String[1];
         if (workSource != null) {
             for (int i = 0; i < workSource.size(); ++i) {
                 uid = workSource.get(i);
@@ -4073,8 +4074,9 @@
                             workSourceName != null ? workSourceName : packageName);
                     pkg.noteWakeupAlarmLocked(tag);
                 }
-
-                StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+                uids[0] = workSource.get(i);
+                tags[0] = workSource.getName(i);
+                StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uids, tags, tag);
             }
 
             ArrayList<WorkChain> workChains = workSource.getWorkChains();
@@ -4087,9 +4089,7 @@
                         BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName);
                         pkg.noteWakeupAlarmLocked(tag);
                     }
-
-                    // TODO(statsd): Log the full attribution chain here once it's available
-                    StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+                    StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, wc.getUids(), wc.getTags(), tag);
                 }
             }
         } else {
@@ -4097,7 +4097,9 @@
                 BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName);
                 pkg.noteWakeupAlarmLocked(tag);
             }
-            StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+            uids[0] = uid;
+            tags[0] = null;
+            StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uids, tags, tag);
         }
     }
 
@@ -4221,6 +4223,10 @@
             if (wc != null) {
                 StatsLog.write(
                         StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 1);
+            } else {
+                final int[] uids = new int[] { uid };
+                final String[] tags = new String[] { null };
+                StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, type, name, 1);
             }
         }
     }
@@ -4261,6 +4267,10 @@
             if (wc != null) {
                 StatsLog.write(
                         StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 0);
+            } else {
+                final int[] uids = new int[] { uid };
+                final String[] tags = new String[] { null };
+                StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, type, name, 0);
             }
         }
     }
@@ -4354,18 +4364,27 @@
     }
 
     public void noteLongPartialWakelockStart(String name, String historyName, int uid) {
+        final int[] uids = new int[] { uid };
+        final String[] tags = new String[] { null };
+        StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+                uids, tags, name, historyName, 1);
+
         uid = mapUid(uid);
         noteLongPartialWakeLockStartInternal(name, historyName, uid);
-        StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 1);
     }
 
     public void noteLongPartialWakelockStartFromSource(String name, String historyName,
             WorkSource workSource) {
         final int N = workSource.size();
+        final int[] uids = new int[1];
+        final String[] tags = new String[1];
         for (int i = 0; i < N; ++i) {
             final int uid = mapUid(workSource.get(i));
             noteLongPartialWakeLockStartInternal(name, historyName, uid);
-            StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 1);
+            uids[0] = workSource.get(i);
+            tags[0] = workSource.getName(i);
+            StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uids, tags, name,
+                    historyName, 1);
         }
 
         final ArrayList<WorkChain> workChains = workSource.getWorkChains();
@@ -4375,9 +4394,8 @@
                 final int uid = workChain.getAttributionUid();
                 noteLongPartialWakeLockStartInternal(name, historyName, uid);
 
-                // TODO(statsd): the Log WorkChain to statds, and not just the uid.
-                StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName,
-                        1);
+                StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+                        workChain.getUids(), workChain.getTags(), name, historyName, 1);
             }
         }
     }
@@ -4397,18 +4415,27 @@
     }
 
     public void noteLongPartialWakelockFinish(String name, String historyName, int uid) {
+        int[] uids = new int[] { uid };
+        String[] tags = new String[] { null };
+        StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+                uids, tags, name, historyName, 0);
+
         uid = mapUid(uid);
         noteLongPartialWakeLockFinishInternal(name, historyName, uid);
-        StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 0);
     }
 
     public void noteLongPartialWakelockFinishFromSource(String name, String historyName,
             WorkSource workSource) {
         final int N = workSource.size();
+        final int[] uids = new int[1];
+        final String[] tags = new String[1];
         for (int i = 0; i < N; ++i) {
             final int uid = mapUid(workSource.get(i));
             noteLongPartialWakeLockFinishInternal(name, historyName, uid);
-            StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 0);
+            uids[0] = workSource.get(i);
+            tags[0] = workSource.getName(i);
+            StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+                    uids, tags, name, historyName, 0);
         }
 
         final ArrayList<WorkChain> workChains = workSource.getWorkChains();
@@ -4416,12 +4443,9 @@
             for (int i = 0; i < workChains.size(); ++i) {
                 final WorkChain workChain = workChains.get(i);
                 final int uid = workChain.getAttributionUid();
-
                 noteLongPartialWakeLockFinishInternal(name, historyName, uid);
-
-                // TODO(statsd): the Log WorkChain to statds, and not just the uid.
-                StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName,
-                        0);
+                StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+                        workChain.getUids(), workChain.getTags(), name, historyName, 0);
             }
         }
     }
@@ -5388,10 +5412,20 @@
         }
         mBluetoothScanNesting++;
 
-        // TODO(statsd): Log WorkChain here if it's non-null.
-        StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uid, 1);
-        if (isUnoptimized) {
-            StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, 1);
+        if (workChain != null) {
+            StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED,
+                    workChain.getUids(), workChain.getTags(), 1);
+            if (isUnoptimized) {
+                StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED,
+                        workChain.getUids(), workChain.getTags(), 1);
+            }
+        } else {
+            final int[] uids = new int[] {uid};
+            final String[] tags = new String[] {null};
+            StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uids, tags, 1);
+            if (isUnoptimized) {
+                StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uids, tags, 1);
+            }
         }
 
         getUidStatsLocked(uid).noteBluetoothScanStartedLocked(elapsedRealtime, isUnoptimized);
@@ -5428,10 +5462,20 @@
             mBluetoothScanTimer.stopRunningLocked(elapsedRealtime);
         }
 
-        // TODO(statsd): Log WorkChain here if it's non-null.
-        StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uid, 0);
-        if (isUnoptimized) {
-            StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, 0);
+        if (workChain != null) {
+            StatsLog.write(
+                    StatsLog.BLE_SCAN_STATE_CHANGED, workChain.getUids(), workChain.getTags(), 0);
+            if (isUnoptimized) {
+                StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED,
+                        workChain.getUids(), workChain.getTags(), 0);
+            }
+        } else {
+            final int[] uids = new int[] { uid };
+            final String[] tags = new String[] {null};
+            StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uids, tags, 0);
+            if (isUnoptimized) {
+                StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uids, tags, 0);
+            }
         }
 
         getUidStatsLocked(uid).noteBluetoothScanStoppedLocked(elapsedRealtime, isUnoptimized);
@@ -5478,27 +5522,23 @@
                 BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
                 uid.noteResetBluetoothScanLocked(elapsedRealtime);
 
-                StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uid.getUid(), 0);
-
                 List<WorkChain> allWorkChains = uid.getAllBluetoothWorkChains();
                 if (allWorkChains != null) {
                     for (int j = 0; j < allWorkChains.size(); ++j) {
-                        // TODO(statsd) : Log the entire workchain here.
                         StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED,
-                                allWorkChains.get(j).getAttributionUid(), 0);
+                                allWorkChains.get(j).getUids(),
+                                allWorkChains.get(j).getTags(), 0);
                     }
-
                     allWorkChains.clear();
                 }
 
                 List<WorkChain> unoptimizedWorkChains = uid.getUnoptimizedBluetoothWorkChains();
                 if (unoptimizedWorkChains != null) {
                     for (int j = 0; j < unoptimizedWorkChains.size(); ++j) {
-                        // TODO(statsd) : Log the entire workchain here.
                         StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED,
-                                unoptimizedWorkChains.get(j).getAttributionUid(), 0);
+                                unoptimizedWorkChains.get(j).getUids(),
+                                unoptimizedWorkChains.get(j).getTags(), 0);
                     }
-
                     unoptimizedWorkChains.clear();
                 }
             }
@@ -5507,10 +5547,14 @@
 
     public void noteBluetoothScanResultsFromSourceLocked(WorkSource ws, int numNewResults) {
         final int N = ws.size();
+        final int[] uids = new int[1];
+        final String[] tags = new String[1];
         for (int i = 0; i < N; i++) {
             int uid = mapUid(ws.get(i));
             getUidStatsLocked(uid).noteBluetoothScanResultsLocked(numNewResults);
-            StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, uid, numNewResults);
+            uids[0] = ws.get(i);
+            tags[0] = ws.getName(i);
+            StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, uids, tags, numNewResults);
         }
 
         final List<WorkChain> workChains = ws.getWorkChains();
@@ -5519,8 +5563,8 @@
                 final WorkChain wc = workChains.get(i);
                 int uid = mapUid(wc.getAttributionUid());
                 getUidStatsLocked(uid).noteBluetoothScanResultsLocked(numNewResults);
-                // TODO(statsd): Log the entire WorkChain here.
-                StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, uid, numNewResults);
+                StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED,
+                        wc.getUids(), wc.getTags(), numNewResults);
             }
         }
     }
@@ -5835,10 +5879,14 @@
 
     public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) {
         int N = ws.size();
+        final int[] uids = new int[1];
+        final String[] tags = new String[1];
         for (int i=0; i<N; i++) {
             final int uid = mapUid(ws.get(i));
             noteFullWifiLockAcquiredLocked(uid);
-            StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, 1);
+            uids[0] = ws.get(i);
+            tags[0] = ws.getName(i);
+            StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uids, tags, 1);
         }
 
         final List<WorkChain> workChains = ws.getWorkChains();
@@ -5847,22 +5895,22 @@
                 final WorkChain workChain = workChains.get(i);
                 final int uid = mapUid(workChain.getAttributionUid());
                 noteFullWifiLockAcquiredLocked(uid);
-
-                // TODO(statsd): Log workChain instead of uid here.
-                if (DEBUG) {
-                    Slog.v(TAG, "noteFullWifiLockAcquiredFromSourceLocked: " + workChain);
-                }
-                StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, 1);
+                StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED,
+                        workChain.getUids(), workChain.getTags(), 1);
             }
         }
     }
 
     public void noteFullWifiLockReleasedFromSourceLocked(WorkSource ws) {
         int N = ws.size();
+        final int[] uids = new int[1];
+        final String[] tags = new String[1];
         for (int i=0; i<N; i++) {
             final int uid = mapUid(ws.get(i));
             noteFullWifiLockReleasedLocked(uid);
-            StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, 0);
+            uids[0] = ws.get(i);
+            tags[0] = ws.getName(i);
+            StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uids, tags, 0);
         }
 
         final List<WorkChain> workChains = ws.getWorkChains();
@@ -5871,22 +5919,22 @@
                 final WorkChain workChain = workChains.get(i);
                 final int uid = mapUid(workChain.getAttributionUid());
                 noteFullWifiLockReleasedLocked(uid);
-
-                // TODO(statsd): Log workChain instead of uid here.
-                if (DEBUG) {
-                    Slog.v(TAG, "noteFullWifiLockReleasedFromSourceLocked: " + workChain);
-                }
-                StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, 0);
+                StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED,
+                        workChain.getUids(), workChain.getTags(), 0);
             }
         }
     }
 
     public void noteWifiScanStartedFromSourceLocked(WorkSource ws) {
         int N = ws.size();
+        final int[] uids = new int[1];
+        final String[] tags = new String[1];
         for (int i=0; i<N; i++) {
             final int uid = mapUid(ws.get(i));
             noteWifiScanStartedLocked(uid);
-            StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uid, 1);
+            uids[0] = ws.get(i);
+            tags[0] = ws.getName(i);
+            StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uids, tags, 1);
         }
 
         final List<WorkChain> workChains = ws.getWorkChains();
@@ -5895,22 +5943,22 @@
                 final WorkChain workChain = workChains.get(i);
                 final int uid = mapUid(workChain.getAttributionUid());
                 noteWifiScanStartedLocked(uid);
-
-                // TODO(statsd): Log workChain instead of uid here.
-                if (DEBUG) {
-                    Slog.v(TAG, "noteWifiScanStartedFromSourceLocked: " + workChain);
-                }
-                StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uid, 1);
+                StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, workChain.getUids(),
+                        workChain.getTags(), 1);
             }
         }
     }
 
     public void noteWifiScanStoppedFromSourceLocked(WorkSource ws) {
         int N = ws.size();
+        final int[] uids = new int[1];
+        final String[] tags = new String[1];
         for (int i=0; i<N; i++) {
             final int uid = mapUid(ws.get(i));
             noteWifiScanStoppedLocked(uid);
-            StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uid, 0);
+            uids[0] = ws.get(i);
+            tags[0] = ws.getName(i);
+            StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uids, tags, 0);
         }
 
         final List<WorkChain> workChains = ws.getWorkChains();
@@ -5919,11 +5967,8 @@
                 final WorkChain workChain = workChains.get(i);
                 final int uid = mapUid(workChain.getAttributionUid());
                 noteWifiScanStoppedLocked(uid);
-
-                if (DEBUG) {
-                    Slog.v(TAG, "noteWifiScanStoppedFromSourceLocked: " + workChain);
-                }
-                StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uid, 0);
+                StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED,
+                        workChain.getUids(), workChain.getTags(), 0);
             }
         }
     }
@@ -5956,34 +6001,6 @@
         }
     }
 
-    public void noteWifiMulticastEnabledFromSourceLocked(WorkSource ws) {
-        int N = ws.size();
-        for (int i=0; i<N; i++) {
-            noteWifiMulticastEnabledLocked(ws.get(i));
-        }
-
-        final List<WorkChain> workChains = ws.getWorkChains();
-        if (workChains != null) {
-            for (int i = 0; i < workChains.size(); ++i) {
-                noteWifiMulticastEnabledLocked(workChains.get(i).getAttributionUid());
-            }
-        }
-    }
-
-    public void noteWifiMulticastDisabledFromSourceLocked(WorkSource ws) {
-        int N = ws.size();
-        for (int i=0; i<N; i++) {
-            noteWifiMulticastDisabledLocked(ws.get(i));
-        }
-
-        final List<WorkChain> workChains = ws.getWorkChains();
-        if (workChains != null) {
-            for (int i = 0; i < workChains.size(); ++i) {
-                noteWifiMulticastDisabledLocked(workChains.get(i).getAttributionUid());
-            }
-        }
-    }
-
     private static String[] includeInStringArray(String[] array, String str) {
         if (ArrayUtils.indexOf(array, str) >= 0) {
             return array;
@@ -6917,16 +6934,18 @@
 
         public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) {
             createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
-            // TODO(statsd): Possibly use a worksource instead of a uid.
-            StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 1);
+            final int[] uids = new int[] { getUid() };
+            final String[] tags = new String[] { null };
+            StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 1);
         }
 
         public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) {
             if (mAudioTurnedOnTimer != null) {
                 mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
                 if (!mAudioTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
-                    // TODO(statsd): Possibly use a worksource instead of a uid.
-                    StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 0);
+                    final int[] uids = new int[] { getUid() };
+                    final String[] tags = new String[] { null };
+                    StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 0);
                 }
             }
         }
@@ -6934,8 +6953,9 @@
         public void noteResetAudioLocked(long elapsedRealtimeMs) {
             if (mAudioTurnedOnTimer != null) {
                 mAudioTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
-                // TODO(statsd): Possibly use a worksource instead of a uid.
-                StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 0);
+                final int[] uids = new int[] { getUid() };
+                final String[] tags = new String[] { null };
+                StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 0);
             }
         }
 
@@ -6949,16 +6969,18 @@
 
         public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) {
             createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
-            // TODO(statsd): Possibly use a worksource instead of a uid.
-            StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 1);
+            final int[] uids = new int[] { getUid() };
+            final String[] tags = new String[] { null };
+            StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 1);
         }
 
         public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) {
             if (mVideoTurnedOnTimer != null) {
                 mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
                 if (!mVideoTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
-                    // TODO(statsd): Possibly use a worksource instead of a uid.
-                    StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 0);
+                    final int[] uids = new int[] { getUid() };
+                    final String[] tags = new String[] { null };
+                    StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 0);
                 }
             }
         }
@@ -6966,8 +6988,9 @@
         public void noteResetVideoLocked(long elapsedRealtimeMs) {
             if (mVideoTurnedOnTimer != null) {
                 mVideoTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
-                // TODO(statsd): Possibly use a worksource instead of a uid.
-                StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 0);
+                final int[] uids = new int[] { getUid() };
+                final String[] tags = new String[] { null };
+                StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 0);
             }
         }
 
@@ -6981,16 +7004,18 @@
 
         public void noteFlashlightTurnedOnLocked(long elapsedRealtimeMs) {
             createFlashlightTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
-            // TODO(statsd): Possibly use a worksource instead of a uid.
-            StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 1);
+            final int[] uids = new int[] { getUid() };
+            final String[] tags = new String[] { null };
+            StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 1);
         }
 
         public void noteFlashlightTurnedOffLocked(long elapsedRealtimeMs) {
             if (mFlashlightTurnedOnTimer != null) {
                 mFlashlightTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
                 if (!mFlashlightTurnedOnTimer.isRunningLocked()) {
-                    // TODO(statsd): Possibly use a worksource instead of a uid.
-                    StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 0);
+                    final int[] uids = new int[] { getUid() };
+                    final String[] tags = new String[] { null };
+                    StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 0);
                 }
             }
         }
@@ -6998,8 +7023,9 @@
         public void noteResetFlashlightLocked(long elapsedRealtimeMs) {
             if (mFlashlightTurnedOnTimer != null) {
                 mFlashlightTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
-                // TODO(statsd): Possibly use a worksource instead of a uid.
-                StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 0);
+                final int[] uids = new int[] { getUid() };
+                final String[] tags = new String[] { null };
+                StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 0);
             }
         }
 
@@ -7013,16 +7039,18 @@
 
         public void noteCameraTurnedOnLocked(long elapsedRealtimeMs) {
             createCameraTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
-            // TODO(statsd): Possibly use a worksource instead of a uid.
-            StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 1);
+            final int[] uids = new int[] { getUid() };
+            final String[] tags = new String[] { null };
+            StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 1);
         }
 
         public void noteCameraTurnedOffLocked(long elapsedRealtimeMs) {
             if (mCameraTurnedOnTimer != null) {
                 mCameraTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
                 if (!mCameraTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
-                    // TODO(statsd): Possibly use a worksource instead of a uid.
-                    StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 0);
+                    final int[] uids = new int[] { getUid() };
+                    final String[] tags = new String[] { null };
+                    StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 0);
                 }
             }
         }
@@ -7030,8 +7058,9 @@
         public void noteResetCameraLocked(long elapsedRealtimeMs) {
             if (mCameraTurnedOnTimer != null) {
                 mCameraTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
-                // TODO(statsd): Possibly use a worksource instead of a uid.
-                StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 0);
+                final int[] uids = new int[] { getUid() };
+                final String[] tags = new String[] { null };
+                StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 0);
             }
         }
 
@@ -9593,8 +9622,9 @@
             DualTimer t = mSyncStats.startObject(name);
             if (t != null) {
                 t.startRunningLocked(elapsedRealtimeMs);
-                // TODO(statsd): Possibly use a worksource instead of a uid.
-                StatsLog.write(StatsLog.SYNC_STATE_CHANGED, getUid(), name, 1);
+                final int[] uids = new int[] { getUid() };
+                final String[] tags = new String[] { null };
+                StatsLog.write(StatsLog.SYNC_STATE_CHANGED, uids, tags, name, 1);
             }
         }
 
@@ -9603,8 +9633,9 @@
             if (t != null) {
                 t.stopRunningLocked(elapsedRealtimeMs);
                 if (!t.isRunningLocked()) { // only tell statsd if truly stopped
-                    // TODO(statsd): Possibly use a worksource instead of a uid.
-                    StatsLog.write(StatsLog.SYNC_STATE_CHANGED, getUid(), name, 0);
+                    final int[] uids = new int[] { getUid() };
+                    final String[] tags = new String[] { null };
+                    StatsLog.write(StatsLog.SYNC_STATE_CHANGED, uids, tags, name, 0);
                 }
             }
         }
@@ -9613,8 +9644,9 @@
             DualTimer t = mJobStats.startObject(name);
             if (t != null) {
                 t.startRunningLocked(elapsedRealtimeMs);
-                // TODO(statsd): Possibly use a worksource instead of a uid.
-                StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), name, 1);
+                final int[] uids = new int[] { getUid() };
+                final String[] tags = new String[] { null };
+                StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, uids, tags, name, 1);
             }
         }
 
@@ -9623,8 +9655,9 @@
             if (t != null) {
                 t.stopRunningLocked(elapsedRealtimeMs);
                 if (!t.isRunningLocked()) { // only tell statsd if truly stopped
-                    // TODO(statsd): Possibly use a worksource instead of a uid.
-                    StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), name, 0);
+                    final int[] uids = new int[] { getUid() };
+                    final String[] tags = new String[] { null };
+                    StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, uids, tags, name, 0);
                 }
             }
             if (mBsi.mOnBatteryTimeBase.isRunning()) {
@@ -9735,11 +9768,12 @@
         public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
             DualTimer t = getSensorTimerLocked(sensor, /* create= */ true);
             t.startRunningLocked(elapsedRealtimeMs);
-            // TODO(statsd): Possibly use a worksource instead of a uid.
+            final int[] uids = new int[] { getUid() };
+            final String[] tags = new String[] { null };
             if (sensor == Sensor.GPS) {
-                StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), 1);
+                StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, uids, tags, 1);
             } else {
-                StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, getUid(), sensor, 1);
+                StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, uids, tags, sensor, 1);
             }
         }
 
@@ -9750,10 +9784,12 @@
                 t.stopRunningLocked(elapsedRealtimeMs);
                 if (!t.isRunningLocked()) { // only tell statsd if truly stopped
                     // TODO(statsd): Possibly use a worksource instead of a uid.
+                    final int[] uids = new int[] { getUid() };
+                    final String[] tags = new String[] { null };
                     if (sensor == Sensor.GPS) {
-                        StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), 0);
+                        StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, uids, tags, 0);
                     } else {
-                        StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, getUid(), sensor, 0);
+                        StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, uids, tags, sensor, 0);
                     }
                 }
             }
diff --git a/core/java/com/android/internal/os/KernelCpuSpeedReader.java b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
index 4c0370c..98fea01 100644
--- a/core/java/com/android/internal/os/KernelCpuSpeedReader.java
+++ b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
@@ -38,6 +38,7 @@
     private static final String TAG = "KernelCpuSpeedReader";
 
     private final String mProcFile;
+    private final int mNumSpeedSteps;
     private final long[] mLastSpeedTimesMs;
     private final long[] mDeltaSpeedTimesMs;
 
@@ -50,6 +51,7 @@
     public KernelCpuSpeedReader(int cpuNumber, int numSpeedSteps) {
         mProcFile = String.format("/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state",
                 cpuNumber);
+        mNumSpeedSteps = numSpeedSteps;
         mLastSpeedTimesMs = new long[numSpeedSteps];
         mDeltaSpeedTimesMs = new long[numSpeedSteps];
         long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
@@ -90,4 +92,31 @@
         }
         return mDeltaSpeedTimesMs;
     }
+
+    /**
+     * @return The time (in milliseconds) spent at different cpu speeds. The values should be
+     * monotonically increasing, unless the cpu was hotplugged.
+     */
+    public long[] readAbsolute() {
+        StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
+        long[] speedTimeMs = new long[mNumSpeedSteps];
+        try (BufferedReader reader = new BufferedReader(new FileReader(mProcFile))) {
+            TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
+            String line;
+            int speedIndex = 0;
+            while (speedIndex < mNumSpeedSteps && (line = reader.readLine()) != null) {
+                splitter.setString(line);
+                splitter.next();
+                long time = Long.parseLong(splitter.next()) * mJiffyMillis;
+                speedTimeMs[speedIndex] = time;
+                speedIndex++;
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to read cpu-freq: " + e.getMessage());
+            Arrays.fill(speedTimeMs, 0);
+        } finally {
+            StrictMode.setThreadPolicy(policy);
+        }
+        return speedTimeMs;
+    }
 }
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index 69184c3..e5d5685 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -35,7 +35,7 @@
 
     void addStateMonitorCallback(IKeyguardStateCallback callback);
     void verifyUnlock(IKeyguardExitCallback callback);
-    void dismiss(IKeyguardDismissCallback callback);
+    void dismiss(IKeyguardDismissCallback callback, CharSequence message);
     void onDreamingStarted();
     void onDreamingStopped();
 
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index aa85668..7b023f4 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -184,6 +184,13 @@
     }
 
     /**
+     * Length of the given collection or 0 if it's null.
+     */
+    public static int size(@Nullable Collection<?> collection) {
+        return collection == null ? 0 : collection.size();
+    }
+
+    /**
      * Checks that value is present as at least one of the elements of the array.
      * @param array the array to check in
      * @param value the value to check for
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 7985e57..2f2c747 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.util;
 
-import static com.android.internal.util.ArrayUtils.isEmpty;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.ArraySet;
@@ -173,13 +171,20 @@
     }
 
     /**
-     * Returns the size of the given list, or 0 if the list is null
+     * Returns the size of the given collection, or 0 if null
      */
     public static int size(@Nullable Collection<?> cur) {
         return cur != null ? cur.size() : 0;
     }
 
     /**
+     * Returns whether the given collection {@link Collection#isEmpty is empty} or {@code null}
+     */
+    public static boolean isEmpty(@Nullable Collection<?> cur) {
+        return size(cur) == 0;
+    }
+
+    /**
      * Returns the elements of the given list that are of type {@code c}
      */
     public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
diff --git a/core/java/com/android/internal/util/ObjectUtils.java b/core/java/com/android/internal/util/ObjectUtils.java
index 59e5a64..379602a 100644
--- a/core/java/com/android/internal/util/ObjectUtils.java
+++ b/core/java/com/android/internal/util/ObjectUtils.java
@@ -29,6 +29,9 @@
         return a != null ? a : Preconditions.checkNotNull(b);
     }
 
+    /**
+     * Compares two {@link Nullable} objects with {@code null} values considered the smallest
+     */
     public static <T extends Comparable> int compare(@Nullable T a, @Nullable T b) {
         if (a != null) {
             return (b != null) ? a.compareTo(b) : 1;
@@ -36,4 +39,13 @@
             return (b != null) ? -1 : 0;
         }
     }
+
+    /**
+     * @return {@code null} if the given instance is not of the given calss, or the given
+     *         instance otherwise
+     */
+    @Nullable
+    public static <S, T extends S> T castOrNull(@Nullable S instance, @NonNull Class<T> c) {
+        return c.isInstance(instance) ? (T) instance : null;
+    }
 }
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
new file mode 100644
index 0000000..f7ea787
--- /dev/null
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -0,0 +1,128 @@
+package com.android.internal.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+public class ScreenshotHelper {
+    private static final String TAG = "ScreenshotHelper";
+
+    private static final String SYSUI_PACKAGE = "com.android.systemui";
+    private static final String SYSUI_SCREENSHOT_SERVICE =
+            "com.android.systemui.screenshot.TakeScreenshotService";
+    private static final String SYSUI_SCREENSHOT_ERROR_RECEIVER =
+            "com.android.systemui.screenshot.ScreenshotServiceErrorReceiver";
+
+    // Time until we give up on the screenshot & show an error instead.
+    private final int SCREENSHOT_TIMEOUT_MS = 10000;
+
+    private final Object mScreenshotLock = new Object();
+    private ServiceConnection mScreenshotConnection = null;
+    private final Context mContext;
+
+    public ScreenshotHelper(Context context) {
+        mContext = context;
+    }
+
+    public void takeScreenshot(final int screenshotType, final boolean hasStatus,
+            final boolean hasNav, Handler handler) {
+        synchronized (mScreenshotLock) {
+            if (mScreenshotConnection != null) {
+                return;
+            }
+            final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
+                    SYSUI_SCREENSHOT_SERVICE);
+            final Intent serviceIntent = new Intent();
+
+            final Runnable mScreenshotTimeout = new Runnable() {
+                @Override public void run() {
+                    synchronized (mScreenshotLock) {
+                        if (mScreenshotConnection != null) {
+                            mContext.unbindService(mScreenshotConnection);
+                            mScreenshotConnection = null;
+                            notifyScreenshotError();
+                        }
+                    }
+                }
+            };
+
+            serviceIntent.setComponent(serviceComponent);
+            ServiceConnection conn = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                    synchronized (mScreenshotLock) {
+                        if (mScreenshotConnection != this) {
+                            return;
+                        }
+                        Messenger messenger = new Messenger(service);
+                        Message msg = Message.obtain(null, screenshotType);
+                        final ServiceConnection myConn = this;
+                        Handler h = new Handler(handler.getLooper()) {
+                            @Override
+                            public void handleMessage(Message msg) {
+                                synchronized (mScreenshotLock) {
+                                    if (mScreenshotConnection == myConn) {
+                                        mContext.unbindService(mScreenshotConnection);
+                                        mScreenshotConnection = null;
+                                        handler.removeCallbacks(mScreenshotTimeout);
+                                    }
+                                }
+                            }
+                        };
+                        msg.replyTo = new Messenger(h);
+                        msg.arg1 = hasStatus ? 1: 0;
+                        msg.arg2 = hasNav ? 1: 0;
+                        try {
+                            messenger.send(msg);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Couldn't take screenshot: " + e);
+                        }
+                    }
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                    synchronized (mScreenshotLock) {
+                        if (mScreenshotConnection != null) {
+                            mContext.unbindService(mScreenshotConnection);
+                            mScreenshotConnection = null;
+                            handler.removeCallbacks(mScreenshotTimeout);
+                            notifyScreenshotError();
+                        }
+                    }
+                }
+            };
+            if (mContext.bindServiceAsUser(serviceIntent, conn,
+                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+                    UserHandle.CURRENT)) {
+                mScreenshotConnection = conn;
+                handler.postDelayed(mScreenshotTimeout, SCREENSHOT_TIMEOUT_MS);
+            }
+        }
+    }
+
+    /**
+     * Notifies the screenshot service to show an error.
+     */
+    private void notifyScreenshotError() {
+        // If the service process is killed, then ask it to clean up after itself
+        final ComponentName errorComponent = new ComponentName(SYSUI_PACKAGE,
+                SYSUI_SCREENSHOT_ERROR_RECEIVER);
+        // Broadcast needs to have a valid action.  We'll just pick
+        // a generic one, since the receiver here doesn't care.
+        Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT);
+        errorIntent.setComponent(errorComponent);
+        errorIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
+                Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 4e7df28..31d22e0 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -19,9 +19,9 @@
 import android.app.PendingIntent;
 import android.app.trust.IStrongAuthTracker;
 import android.os.Bundle;
-import android.security.recoverablekeystore.KeyEntryRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.security.keystore.EntryRecoveryData;
+import android.security.keystore.RecoveryData;
+import android.security.keystore.RecoveryMetadata;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
 import com.android.internal.widget.VerifyCredentialResponse;
 
@@ -60,25 +60,25 @@
             in byte[] token, int requestedQuality, int userId);
     void unlockUserWithToken(long tokenHandle, in byte[] token, int userId);
 
-    // RecoverableKeyStoreLoader methods.
+    // Keystore RecoveryManager methods.
     // {@code ServiceSpecificException} may be thrown to signal an error, which caller can
-    // convert to  {@code RecoverableKeyStoreLoader}.
+    // convert to  {@code RecoveryManagerException}.
     void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList);
-    KeyStoreRecoveryData getRecoveryData(in byte[] account);
+    RecoveryData getRecoveryData(in byte[] account);
     byte[] generateAndStoreKey(String alias);
     void removeKey(String alias);
     void setSnapshotCreatedPendingIntent(in PendingIntent intent);
     Map getRecoverySnapshotVersions();
-    void setServerParameters(long serverParameters);
+    void setServerParams(in byte[] serverParams);
     void setRecoveryStatus(in String packageName, in String[] aliases, int status);
     Map getRecoveryStatus(in String packageName);
     void setRecoverySecretTypes(in int[] secretTypes);
     int[] getRecoverySecretTypes();
     int[] getPendingRecoverySecretTypes();
-    void recoverySecretAvailable(in KeyStoreRecoveryMetadata recoverySecret);
+    void recoverySecretAvailable(in RecoveryMetadata recoverySecret);
     byte[] startRecoverySession(in String sessionId,
             in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge,
-            in List<KeyStoreRecoveryMetadata> secrets);
+            in List<RecoveryMetadata> secrets);
     Map/*<String, byte[]>*/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob,
-            in List<KeyEntryRecoveryData> applicationKeys);
+            in List<EntryRecoveryData> applicationKeys);
 }
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 7635a72..6f2246a 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -22,9 +22,7 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -504,7 +502,7 @@
     }
 
     private void onCollapsedChanged(boolean isCollapsed) {
-        notifyViewAccessibilityStateChangedIfNeeded(
+        notifyAccessibilityStateChanged(
                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
 
         if (mScrollIndicatorDrawable != null) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index b3f66e9..96f3308 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -84,7 +84,7 @@
         "android_view_VelocityTracker.cpp",
         "android_text_AndroidCharacter.cpp",
         "android_text_Hyphenator.cpp",
-        "android_text_MeasuredText.cpp",
+        "android_text_MeasuredParagraph.cpp",
         "android_text_StaticLayout.cpp",
         "android_os_Debug.cpp",
         "android_os_GraphicsEnvironment.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 6d7fe05..6569b47 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -178,7 +178,7 @@
 extern int register_android_net_NetworkUtils(JNIEnv* env);
 extern int register_android_text_AndroidCharacter(JNIEnv *env);
 extern int register_android_text_Hyphenator(JNIEnv *env);
-extern int register_android_text_MeasuredText(JNIEnv* env);
+extern int register_android_text_MeasuredParagraph(JNIEnv* env);
 extern int register_android_text_StaticLayout(JNIEnv *env);
 extern int register_android_opengl_classes(JNIEnv *env);
 extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
@@ -1342,7 +1342,7 @@
     REG_JNI(register_android_content_XmlBlock),
     REG_JNI(register_android_text_AndroidCharacter),
     REG_JNI(register_android_text_Hyphenator),
-    REG_JNI(register_android_text_MeasuredText),
+    REG_JNI(register_android_text_MeasuredParagraph),
     REG_JNI(register_android_text_StaticLayout),
     REG_JNI(register_android_view_InputDevice),
     REG_JNI(register_android_view_KeyCharacterMap),
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index ec03f82..a0a4be4 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -67,8 +67,8 @@
         kOpaque      = -1,
     };
 
-    std::unique_ptr<SkAndroidCodec> mCodec;
     NinePatchPeeker mPeeker;
+    std::unique_ptr<SkAndroidCodec> mCodec;
 };
 
 static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index 1eeea51..1659168 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -146,8 +146,8 @@
         return nullptr;
     }
     jobject jMap = env->NewObject(gHashMapClazz, gHashMapInit);
-    for (const Vndk &vndk : manifest->vndks()) {
-        std::string key = to_string(vndk.versionRange());
+    for (const auto &vndk : manifest->vendorNdks()) {
+        std::string key = vndk.version();
         env->CallObjectMethod(jMap, gHashMapPut,
                 env->NewStringUTF(key.c_str()), toJavaStringArray(env, vndk.libraries()));
     }
diff --git a/core/jni/android_text_MeasuredText.cpp b/core/jni/android_text_MeasuredParagraph.cpp
similarity index 83%
rename from core/jni/android_text_MeasuredText.cpp
rename to core/jni/android_text_MeasuredParagraph.cpp
index af9d131..bdae0b2 100644
--- a/core/jni/android_text_MeasuredText.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "MeasuredText"
+#define LOG_TAG "MeasuredParagraph"
 
 #include "ScopedIcuLocale.h"
 #include "unicode/locid.h"
@@ -49,7 +49,7 @@
     return reinterpret_cast<Paint*>(ptr);
 }
 
-static inline minikin::MeasuredText* toMeasuredText(jlong ptr) {
+static inline minikin::MeasuredText* toMeasuredParagraph(jlong ptr) {
     return reinterpret_cast<minikin::MeasuredText*>(ptr);
 }
 
@@ -57,8 +57,8 @@
     return reinterpret_cast<jlong>(ptr);
 }
 
-static void releaseMeasuredText(jlong measuredTextPtr) {
-    delete toMeasuredText(measuredTextPtr);
+static void releaseMeasuredParagraph(jlong measuredTextPtr) {
+    delete toMeasuredParagraph(measuredTextPtr);
 }
 
 // Regular JNI
@@ -84,7 +84,7 @@
 }
 
 // Regular JNI
-static jlong nBuildNativeMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr,
+static jlong nBuildNativeMeasuredParagraph(JNIEnv* env, jclass /* unused */, jlong builderPtr,
                                       jcharArray javaText) {
     ScopedCharArrayRO text(env, javaText);
     const minikin::U16StringPiece textBuffer(text.get(), text.size());
@@ -100,23 +100,23 @@
 
 // CriticalNative
 static jlong nGetReleaseFunc() {
-    return toJLong(&releaseMeasuredText);
+    return toJLong(&releaseMeasuredParagraph);
 }
 
 static const JNINativeMethod gMethods[] = {
-    // MeasuredTextBuilder native functions.
+    // MeasuredParagraphBuilder native functions.
     {"nInitBuilder", "()J", (void*) nInitBuilder},
     {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
     {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
-    {"nBuildNativeMeasuredText", "(J[C)J", (void*) nBuildNativeMeasuredText},
+    {"nBuildNativeMeasuredParagraph", "(J[C)J", (void*) nBuildNativeMeasuredParagraph},
     {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
 
-    // MeasuredText native functions.
+    // MeasuredParagraph native functions.
     {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc},  // Critical Natives
 };
 
-int register_android_text_MeasuredText(JNIEnv* env) {
-    return RegisterMethodsOrDie(env, "android/text/MeasuredText", gMethods, NELEM(gMethods));
+int register_android_text_MeasuredParagraph(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, "android/text/MeasuredParagraph", gMethods, NELEM(gMethods));
 }
 
 }
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index b5c23df..682dc873 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -174,7 +174,7 @@
 
         // Inputs
         "[C"  // text
-        "J"  // MeasuredText ptr.
+        "J"  // MeasuredParagraph ptr.
         "I"  // length
         "F"  // firstWidth
         "I"  // firstWidthLineCount
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 5e5d59b..0ef5445 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -72,7 +72,6 @@
 
 // Implements SkMallocPixelRef::ReleaseProc, to delete the screenshot on unref.
 void DeleteScreenshot(void* addr, void* context) {
-    SkASSERT(addr == ((ScreenshotClient*) context)->getPixels());
     delete ((ScreenshotClient*) context);
 }
 
diff --git a/core/proto/android/app/statusbarmanager.proto b/core/proto/android/app/statusbarmanager.proto
new file mode 100644
index 0000000..3d1447a
--- /dev/null
+++ b/core/proto/android/app/statusbarmanager.proto
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.app;
+option java_multiple_files = true;
+
+message StatusBarManagerProto {
+  enum WindowState {
+    WINDOW_STATE_SHOWING = 0;
+    WINDOW_STATE_HIDING = 1;
+    WINDOW_STATE_HIDDEN = 2;
+  }
+  enum TransientWindowState {
+    TRANSIENT_BAR_NONE = 0;
+    TRANSIENT_BAR_SHOW_REQUESTED = 1;
+    TRANSIENT_BAR_SHOWING = 2;
+    TRANSIENT_BAR_HIDING = 3;
+  }
+}
diff --git a/core/proto/android/content/activityinfo.proto b/core/proto/android/content/activityinfo.proto
new file mode 100644
index 0000000..012752a
--- /dev/null
+++ b/core/proto/android/content/activityinfo.proto
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.content;
+option java_multiple_files = true;
+
+message ActivityInfoProto {
+  enum ScreenOrientation {
+    SCREEN_ORIENTATION_UNSET = -2;
+    SCREEN_ORIENTATION_UNSPECIFIED = -1;
+    SCREEN_ORIENTATION_LANDSCAPE = 0;
+    SCREEN_ORIENTATION_PORTRAIT = 1;
+    SCREEN_ORIENTATION_USER = 2;
+    SCREEN_ORIENTATION_BEHIND = 3;
+    SCREEN_ORIENTATION_SENSOR = 4;
+    SCREEN_ORIENTATION_NOSENSOR = 5;
+    SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6;
+    SCREEN_ORIENTATION_SENSOR_PORTRAIT = 7;
+    SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8;
+    SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9;
+    SCREEN_ORIENTATION_FULL_SENSOR = 10;
+    SCREEN_ORIENTATION_USER_LANDSCAPE = 11;
+    SCREEN_ORIENTATION_USER_PORTRAIT = 12;
+    SCREEN_ORIENTATION_FULL_USER = 13;
+    SCREEN_ORIENTATION_LOCKED = 14;
+  }
+}
+
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index 9999b4e..331f80f 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -674,14 +674,13 @@
     // needed:
     // top > foreground service > foreground > background > top sleeping > heavy weight > cache
     enum State {
-      // Time this uid has any processes in the top state (or above such as
-      // persistent).
+      // Time this uid has any processes in the top state.
       PROCESS_STATE_TOP = 0;
-      // Time this uid has any process with a started out bound foreground
-      // service, but none in the "top" state.
+      // Time this uid has any process with a started foreground service, but
+      // none in the "top" state.
       PROCESS_STATE_FOREGROUND_SERVICE = 1;
-      // Time this uid has any process in an active foreground state, but none
-      // in the "top sleeping" or better state.
+      // Time this uid has any process in an active foreground state, but none in the
+      // "foreground service" or better state. Persistent and other foreground states go here.
       PROCESS_STATE_FOREGROUND = 2;
       // Time this uid has any process in an active background state, but none
       // in the "foreground" or better state.
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index f5d098c..d4bdb9b 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -388,8 +388,9 @@
     optional SettingProto enable_deletion_helper_no_threshold_toggle = 340;
     optional SettingProto notification_snooze_options = 341;
     optional SettingProto enable_gnss_raw_meas_full_tracking = 346;
+    optional SettingProto zram_enabled = 347;
 
-    // Next tag = 347;
+    // Next tag = 348;
 }
 
 message SecureSettingsProto {
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 04bdc5c..71f33c7 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -16,12 +16,15 @@
 
 syntax = "proto2";
 
+import "frameworks/base/core/proto/android/app/statusbarmanager.proto";
+import "frameworks/base/core/proto/android/content/activityinfo.proto";
 import "frameworks/base/core/proto/android/content/configuration.proto";
 import "frameworks/base/core/proto/android/graphics/rect.proto";
 import "frameworks/base/core/proto/android/server/appwindowthumbnail.proto";
 import "frameworks/base/core/proto/android/server/surfaceanimator.proto";
 import "frameworks/base/core/proto/android/view/displaycutout.proto";
 import "frameworks/base/core/proto/android/view/displayinfo.proto";
+import "frameworks/base/core/proto/android/view/surface.proto";
 import "frameworks/base/core/proto/android/view/windowlayoutparams.proto";
 
 package com.android.server.wm.proto;
@@ -49,8 +52,62 @@
   repeated IdentifierProto windows = 3;
 }
 
+message BarControllerProto {
+  optional .android.app.StatusBarManagerProto.WindowState state = 1;
+  optional .android.app.StatusBarManagerProto.TransientWindowState transient_state = 2;
+}
+
+message WindowOrientationListenerProto {
+  optional bool enabled = 1;
+  optional .android.view.SurfaceProto.Rotation rotation = 2;
+}
+
+message KeyguardServiceDelegateProto {
+  optional bool showing = 1;
+  optional bool occluded = 2;
+  optional bool secure = 3;
+  enum ScreenState {
+    SCREEN_STATE_OFF = 0;
+    SCREEN_STATE_TURNING_ON = 1;
+    SCREEN_STATE_ON = 2;
+    SCREEN_STATE_TURNING_OFF = 3;
+  }
+  optional ScreenState screen_state = 4;
+  enum InteractiveState {
+    INTERACTIVE_STATE_SLEEP = 0;
+    INTERACTIVE_STATE_WAKING = 1;
+    INTERACTIVE_STATE_AWAKE = 2;
+    INTERACTIVE_STATE_GOING_TO_SLEEP = 3;
+  }
+  optional InteractiveState interactive_state = 5;
+}
+
 /* represents PhoneWindowManager */
 message WindowManagerPolicyProto {
+  optional int32 last_system_ui_flags = 1;
+  enum UserRotationMode {
+    USER_ROTATION_FREE = 0;
+    USER_ROTATION_LOCKED = 1;
+  }
+  optional UserRotationMode rotation_mode = 2;
+  optional .android.view.SurfaceProto.Rotation rotation = 3;
+  optional .android.content.ActivityInfoProto.ScreenOrientation orientation = 4;
+  optional bool screen_on_fully = 5;
+  optional bool keyguard_draw_complete = 6;
+  optional bool window_manager_draw_complete = 7;
+  optional string focused_app_token = 8;
+  optional IdentifierProto focused_window = 9;
+  optional IdentifierProto top_fullscreen_opaque_window = 10;
+  optional IdentifierProto top_fullscreen_opaque_or_dimming_window = 11;
+  optional bool keyguard_occluded = 12;
+  optional bool keyguard_occluded_changed = 13;
+  optional bool keyguard_occluded_pending = 14;
+  optional bool force_status_bar = 15;
+  optional bool force_status_bar_from_keyguard = 16;
+  optional BarControllerProto status_bar = 17;
+  optional BarControllerProto navigation_bar = 18;
+  optional WindowOrientationListenerProto orientation_listener = 19;
+  optional KeyguardServiceDelegateProto keyguard_delegate = 20;
 }
 
 /* represents AppTransition */
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/core/proto/android/view/surface.proto
similarity index 67%
copy from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
copy to core/proto/android/view/surface.proto
index bd76051..8f5f695 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/core/proto/android/view/surface.proto
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,7 +14,15 @@
  * limitations under the License.
  */
 
-package android.security.recoverablekeystore;
+syntax = "proto2";
+package android.view;
+option java_multiple_files = true;
 
-/* @hide */
-parcelable KeyStoreRecoveryData;
+message SurfaceProto {
+  enum Rotation {
+    ROTATION_0 = 0;
+    ROTATION_90 = 1;
+    ROTATION_180 = 2;
+    ROTATION_270 = 3;
+  }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4bf1cb0..a3e8f1e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -575,6 +575,7 @@
     <!-- Added in P -->
     <protected-broadcast android:name="android.app.action.PROFILE_OWNER_CHANGED" />
     <protected-broadcast android:name="android.app.action.TRANSFER_OWNERSHIP_COMPLETE" />
+    <protected-broadcast android:name="android.app.action.DATA_SHARING_RESTRICTION_CHANGED" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
@@ -2952,13 +2953,14 @@
 
     <!-- Allows an application to collect usage infomation about brightness slider changes.
          <p>Not for use by third-party applications.</p>
-         TODO: make a System API
-         @hide -->
+         @hide
+         @SystemApi -->
     <permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|development" />
 
     <!-- Allows an application to modify the display brightness configuration
-         @hide -->
+         @hide
+         @SystemApi -->
     <permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"
         android:protectionLevel="signature|privileged|development" />
 
@@ -3308,6 +3310,10 @@
     <permission android:name="android.permission.BIND_PACKAGE_VERIFIER"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi @hide Allows an application to mark other applications as harmful -->
+    <permission android:name="android.permission.SET_HARMFUL_APP_WARNINGS"
+        android:protectionLevel="signature|verifier" />
+
     <!-- @SystemApi @hide Intent filter verifier needs to have this permission before the
          PackageManager will trust it to verify intent filters.
     -->
@@ -3394,6 +3400,12 @@
     <permission android:name="android.permission.PROVIDE_TRUST_AGENT"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to show a message
+         on the keyguard when asking to dismiss it.
+         @hide For security reasons, this is a platform-only permission. -->
+    <permission android:name="android.permission.SHOW_KEYGUARD_MESSAGE"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to launch the trust agent settings activity.
         @hide -->
     <permission android:name="android.permission.LAUNCH_TRUST_AGENT_SETTINGS"
@@ -3873,6 +3885,13 @@
                   android:excludeFromRecents="true">
         </activity>
 
+        <activity android:name="com.android.internal.app.HarmfulAppWarningActivity"
+                  android:theme="@style/Theme.DeviceDefault.Light.Dialog.Alert"
+                  android:excludeFromRecents="true"
+                  android:process=":ui"
+                  android:exported="false">
+        </activity>
+
         <receiver android:name="com.android.server.BootReceiver"
                 android:systemUserOnly="true">
             <intent-filter android:priority="1000">
diff --git a/core/res/res/drawable/ic_screenshot.xml b/core/res/res/drawable/ic_screenshot.xml
new file mode 100644
index 0000000..3074b28
--- /dev/null
+++ b/core/res/res/drawable/ic_screenshot.xml
@@ -0,0 +1,33 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24.0dp"
+    android:height="24.0dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:pathData="M0,0h24v24H0V0z"
+        android:fillColor="#00000000"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M17.0,1.0L7.0,1.0C5.9,1.0 5.0,1.9 5.0,3.0l0.0,18.0c0.0,1.1 0.9,2.0 2.0,2.0l10.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L19.0,3.0C19.0,1.9 18.1,1.0 17.0,1.0zM17.0,20.0L7.0,20.0L7.0,4.0l10.0,0.0L17.0,20.0z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M13.0,6.0l-4.0,0.0 0.0,4.0 1.5,0.0 0.0,-2.5 2.5,0.0z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M11.0,18.0l4.0,0.0 0.0,-4.0 -1.5,0.0 0.0,2.5 -2.5,0.0z"/>
+</vector>
diff --git a/core/res/res/raw/color_fade_frag.frag b/core/res/res/raw/color_fade_frag.frag
index a66a5a7..29975d5 100644
--- a/core/res/res/raw/color_fade_frag.frag
+++ b/core/res/res/raw/color_fade_frag.frag
@@ -3,40 +3,12 @@
 precision mediump float;
 uniform samplerExternalOES texUnit;
 uniform float opacity;
-uniform float saturation;
 uniform float gamma;
 varying vec2 UV;
 
-vec3 rgb2hsl(vec3 rgb)
-{
-    float e = 1.0e-7;
-
-    vec4 p = rgb.g < rgb.b ? vec4(rgb.bg, -1.0, 2.0 / 3.0) : vec4(rgb.gb, 0.0, -1.0 / 3.0);
-    vec4 q = rgb.r < p.x ? vec4(p.xyw, rgb.r) : vec4(rgb.r, p.yzx);
-
-    float v = q.x;
-    float c = v - min(q.w, q.y);
-    float h = abs((q.w - q.y) / (6.0 * c + e) + q.z);
-    float l = v - c * 0.5;
-    float s = c / (1.0 - abs(2.0 * l - 1.0) + e);
-    return clamp(vec3(h, s, l), 0.0, 1.0);
-}
-
-vec3 hsl2rgb(vec3 hsl)
-{
-    vec3 h = vec3(hsl.x * 6.0);
-    vec3 p = abs(h - vec3(3.0, 2.0, 4.0));
-    vec3 q = 2.0 - p;
-
-    vec3 rgb = clamp(vec3(p.x - 1.0, q.yz), 0.0, 1.0);
-    float c = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y;
-    return (rgb - vec3(0.5)) * c + hsl.z;
-}
-
 void main()
 {
     vec4 color = texture2D(texUnit, UV);
-    vec3 hsl = rgb2hsl(color.xyz);
-    vec3 rgb = pow(hsl2rgb(vec3(hsl.x, hsl.y * saturation, hsl.z * opacity)), vec3(gamma));
+    vec3 rgb = pow(color.rgb * opacity, vec3(gamma));
     gl_FragColor = vec4(rgb, 1.0);
 }
diff --git a/core/res/res/values-mcc204-mnc04/config.xml b/core/res/res/values-mcc204-mnc04/config.xml
index c66ed12..4a3bf22 100755
--- a/core/res/res/values-mcc204-mnc04/config.xml
+++ b/core/res/res/values-mcc204-mnc04/config.xml
@@ -25,15 +25,5 @@
     -->
     <integer name="config_mobile_mtu">1358</integer>
 
-    <!--Thresholds for LTE dbm in status bar-->
-    <integer-array translatable="false" name="config_lteDbmThresholds">
-        <item>-140</item>    <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
-        <item>-115</item>    <!-- SIGNAL_STRENGTH_POOR -->
-        <item>-105</item>    <!-- SIGNAL_STRENGTH_MODERATE -->
-        <item>-95</item>     <!-- SIGNAL_STRENGTH_GOOD -->
-        <item>-85</item>     <!-- SIGNAL_STRENGTH_GREAT -->
-        <item>-44</item>
-    </integer-array>
-
     <string translatable="false" name="prohibit_manual_network_selection_in_gobal_mode">true;BAE0000000000000</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc480/config.xml b/core/res/res/values-mcc311-mnc480/config.xml
index 04f182e..cc7daa8 100755
--- a/core/res/res/values-mcc311-mnc480/config.xml
+++ b/core/res/res/values-mcc311-mnc480/config.xml
@@ -51,16 +51,6 @@
 
     <bool name="config_auto_attach_data_on_creation">false</bool>
 
-    <!--Thresholds for LTE dbm in status bar-->
-    <integer-array translatable="false" name="config_lteDbmThresholds">
-        <item>-140</item>    <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
-        <item>-115</item>    <!-- SIGNAL_STRENGTH_POOR -->
-        <item>-105</item>    <!-- SIGNAL_STRENGTH_MODERATE -->
-        <item>-95</item>     <!-- SIGNAL_STRENGTH_GOOD -->
-        <item>-85</item>     <!-- SIGNAL_STRENGTH_GREAT -->
-        <item>-44</item>
-    </integer-array>
-
     <string translatable="false" name="prohibit_manual_network_selection_in_gobal_mode">true</string>
 
     <bool name="config_use_sim_language_file">true</bool>
diff --git a/core/res/res/values-mcc505-mnc01/config.xml b/core/res/res/values-mcc505-mnc01/config.xml
index 5a5b8f7..bc088d1 100644
--- a/core/res/res/values-mcc505-mnc01/config.xml
+++ b/core/res/res/values-mcc505-mnc01/config.xml
@@ -31,16 +31,6 @@
       <item>9</item>
     </integer-array>
 
-    <!--Thresholds for LTE dbm in status bar-->
-    <integer-array translatable="false" name="config_lteDbmThresholds">
-        <item>-140</item>    <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
-        <item>-120</item>    <!-- SIGNAL_STRENGTH_POOR -->
-        <item>-115</item>    <!-- SIGNAL_STRENGTH_MODERATE -->
-        <item>-100</item>    <!-- SIGNAL_STRENGTH_GOOD -->
-        <item>-90</item>     <!-- SIGNAL_STRENGTH_GREAT -->
-        <item>-44</item>
-    </integer-array>
-
     <!-- Configure mobile network MTU. Carrier specific value is set here.
     -->
     <integer name="config_mobile_mtu">1400</integer>
diff --git a/core/res/res/values-mcc505-mnc11/config.xml b/core/res/res/values-mcc505-mnc11/config.xml
deleted file mode 100644
index 6d085c1..0000000
--- a/core/res/res/values-mcc505-mnc11/config.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
-     for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!--Thresholds for LTE dbm in status bar-->
-    <integer-array translatable="false" name="config_lteDbmThresholds">
-        <item>-140</item>    <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
-        <item>-120</item>    <!-- SIGNAL_STRENGTH_POOR -->
-        <item>-115</item>    <!-- SIGNAL_STRENGTH_MODERATE -->
-        <item>-100</item>    <!-- SIGNAL_STRENGTH_GOOD -->
-        <item>-90</item>     <!-- SIGNAL_STRENGTH_GREAT -->
-        <item>-44</item>
-    </integer-array>
-</resources>
diff --git a/core/res/res/values-mcc505-mnc71/config.xml b/core/res/res/values-mcc505-mnc71/config.xml
deleted file mode 100644
index 6d085c1..0000000
--- a/core/res/res/values-mcc505-mnc71/config.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
-     for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!--Thresholds for LTE dbm in status bar-->
-    <integer-array translatable="false" name="config_lteDbmThresholds">
-        <item>-140</item>    <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
-        <item>-120</item>    <!-- SIGNAL_STRENGTH_POOR -->
-        <item>-115</item>    <!-- SIGNAL_STRENGTH_MODERATE -->
-        <item>-100</item>    <!-- SIGNAL_STRENGTH_GOOD -->
-        <item>-90</item>     <!-- SIGNAL_STRENGTH_GREAT -->
-        <item>-44</item>
-    </integer-array>
-</resources>
diff --git a/core/res/res/values-mcc505-mnc72/config.xml b/core/res/res/values-mcc505-mnc72/config.xml
deleted file mode 100644
index 6d085c1..0000000
--- a/core/res/res/values-mcc505-mnc72/config.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
-     for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!--Thresholds for LTE dbm in status bar-->
-    <integer-array translatable="false" name="config_lteDbmThresholds">
-        <item>-140</item>    <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
-        <item>-120</item>    <!-- SIGNAL_STRENGTH_POOR -->
-        <item>-115</item>    <!-- SIGNAL_STRENGTH_MODERATE -->
-        <item>-100</item>    <!-- SIGNAL_STRENGTH_GOOD -->
-        <item>-90</item>     <!-- SIGNAL_STRENGTH_GREAT -->
-        <item>-44</item>
-    </integer-array>
-</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index ee7c795..addbbf5 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3030,6 +3030,10 @@
              value, {@code false}, leaves the screen reader to consider other signals, such as
              focusability or the presence of text, to decide what it focus.-->
         <attr name="screenReaderFocusable" format="boolean" />
+
+        <!-- The title this view should present to accessibility as a pane title.
+             See {@link android.view.View#setAccessibilityPaneTitle(CharSequence)} -->
+        <attr name="accessibilityPaneTitle" format="string" />
     </declare-styleable>
 
     <!-- Attributes that can be assigned to a tag for a particular View. -->
@@ -7756,21 +7760,21 @@
         <attr name="settingsActivity" />
     </declare-styleable>
 
-    <!-- @SystemApi Use <code>trust-agent</code> as the root tag of the XML resource that
+    <!--  Use <code>trust-agent</code> as the root tag of the XML resource that
          describes an {@link android.service.trust.TrustAgentService}, which is
          referenced from its {@link android.service.trust.TrustAgentService#TRUST_AGENT_META_DATA}
          meta-data entry.  Described here are the attributes that can be included in that tag.
          @hide -->
     <declare-styleable name="TrustAgent">
-        <!-- @SystemApi Component name of an activity that allows the user to modify
+        <!--  Component name of an activity that allows the user to modify
              the settings for this trust agent. @hide -->
         <attr name="settingsActivity" />
-        <!-- @SystemApi Title for a preference that allows that user to launch the
+        <!--  Title for a preference that allows that user to launch the
              activity to modify trust agent settings. @hide -->
         <attr name="title" />
-        <!-- @SystemApi Summary for the same preference as the title. @hide -->
+        <!--  Summary for the same preference as the title. @hide -->
         <attr name="summary" />
-        <!-- @SystemApi Whether trust agent can unlock a user profile @hide -->
+        <!--  Whether trust agent can unlock a user profile @hide -->
         <attr name="unlockProfile" format="boolean"/>
     </declare-styleable>
 
@@ -7980,16 +7984,16 @@
          by the enrollment application.
          Described here are the attributes that can be included in that tag.
          @hide
-         @SystemApi -->
+          -->
     <declare-styleable name="VoiceEnrollmentApplication">
-        <!-- A globally unique ID for the keyphrase. @hide @SystemApi -->
+        <!-- A globally unique ID for the keyphrase. @hide  -->
         <attr name="searchKeyphraseId" format="integer" />
-        <!-- The actual keyphrase/hint text, or empty if not keyphrase dependent. @hide @SystemApi -->
+        <!-- The actual keyphrase/hint text, or empty if not keyphrase dependent. @hide  -->
         <attr name="searchKeyphrase" format="string" />
         <!-- A comma separated list of BCP-47 language tag for locales that are supported
-             for this keyphrase, or empty if not locale dependent. @hide @SystemApi -->
+             for this keyphrase, or empty if not locale dependent. @hide  -->
         <attr name="searchKeyphraseSupportedLocales" format="string" />
-        <!-- Flags for supported recognition modes. @hide @SystemApi -->
+        <!-- Flags for supported recognition modes. @hide  -->
         <attr name="searchKeyphraseRecognitionFlags">
             <flag name="none" value="0" />
             <flag name="voiceTrigger" value="0x1" />
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index efbe9c2..287f296 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2568,9 +2568,9 @@
              e.g. name=ro.oem.sku value=MKT210.
              Overlay will be ignored unless system property exists and is
              set to specified value -->
-        <!-- @hide @SystemApi This shouldn't be public. -->
+        <!-- @hide This shouldn't be public. -->
         <attr name="requiredSystemPropertyName" format="string" />
-        <!-- @hide @SystemApi This shouldn't be public. -->
+        <!-- @hide This shouldn't be public. -->
         <attr name="requiredSystemPropertyValue" format="string" />
     </declare-styleable>
 
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index 0413100..f8a77f8 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -77,9 +77,9 @@
     <item name="secondary_content_alpha_material_dark" format="float" type="dimen">.7</item>
     <item name="secondary_content_alpha_material_light" format="float" type="dimen">0.54</item>
 
-    <item name="highlight_alpha_material_light" format="float" type="dimen">0.12</item>
-    <item name="highlight_alpha_material_dark" format="float" type="dimen">0.20</item>
-    <item name="highlight_alpha_material_colored" format="float" type="dimen">0.26</item>
+    <item name="highlight_alpha_material_light" format="float" type="dimen">0.16</item>
+    <item name="highlight_alpha_material_dark" format="float" type="dimen">0.32</item>
+    <item name="highlight_alpha_material_colored" format="float" type="dimen">0.48</item>
 
     <!-- Primary & accent colors -->
     <eat-comment />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2440e9b..e3a910f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2481,6 +2481,7 @@
     <string-array translatable="false" name="config_globalActionsList">
         <item>power</item>
         <item>restart</item>
+        <item>screenshot</item>
         <item>logout</item>
         <item>bugreport</item>
         <item>users</item>
@@ -2667,6 +2668,13 @@
 
     <bool name="config_sms_force_7bit_encoding">false</bool>
 
+    <!-- Number of physical SIM slots on the device. This includes both eSIM and pSIM slots, and
+         is not necessarily the same as the number of phones/logical modems supported by the device.
+         For example, a multi-sim device can have 2 phones/logical modems, but 3 physical slots,
+         or a single SIM device can have 1 phones/logical modems, but 2 physical slots (one eSIM
+         and one pSIM) -->
+    <integer name="config_num_physical_slots">1</integer>
+
     <!--Thresholds for LTE dbm in status bar-->
     <integer-array translatable="false" name="config_lteDbmThresholds">
         <item>-140</item>    <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
@@ -2776,6 +2784,11 @@
          the display's native orientation. -->
     <string translatable="false" name="config_mainBuiltInDisplayCutout"></string>
 
+    <!-- Whether the display cutout region of the main built-in display should be forced to
+         black in software (to avoid aliasing or emulate a cutout that is not physically existent).
+         -->
+    <bool name="config_fillMainBuiltInDisplayCutout">false</bool>
+
     <!-- Ultrasound support for Mic/speaker path -->
     <!-- Whether the default microphone audio source supports near-ultrasound frequencies
          (range of 18 - 21 kHz). -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6ec88dc..58ae76c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2861,6 +2861,7 @@
       <public name="widgetFeatures" />
       <public name="appComponentFactory" />
       <public name="fallbackLineSpacing" />
+      <public name="accessibilityPaneTitle" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0618a82..b2fa294 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -484,6 +484,9 @@
     <!-- label for item that logouts the current user -->
     <string name="global_action_logout">End session</string>
 
+    <!-- label for screenshot item in power menu -->
+    <string name="global_action_screenshot">Screenshot</string>
+
     <!-- Take bug report menu title [CHAR LIMIT=NONE] -->
     <string name="bugreport_title">Take bug report</string>
     <!-- Message in bugreport dialog describing what it does [CHAR LIMIT=NONE] -->
@@ -3030,6 +3033,10 @@
     <string name="wifi_wakeup_onboarding_subtext">When you\'re near a high quality saved network</string>
     <!--Notification action to disable Wi-Fi Wake during onboarding.-->
     <string name="wifi_wakeup_onboarding_action_disable">Don\'t turn back on</string>
+    <!--Notification title for when Wi-Fi Wake enables Wi-Fi.-->
+    <string name="wifi_wakeup_enabled_title">Wi\u2011Fi turned on automatically</string>
+    <!--Notification content for when Wi-Fi Wake enables Wi-Fi. %1$s is the SSID of the nearby saved network that triggered the wakeup. -->
+    <string name="wifi_wakeup_enabled_content">You\u0027re near a saved network: <xliff:g id="network_ssid">%1$s</xliff:g></string>
 
     <!-- A notification is shown when a wifi captive portal network is detected.  This is the notification's title. -->
     <string name="wifi_available_sign_in">Sign in to Wi-Fi network</string>
@@ -4795,4 +4802,11 @@
 
     <!--Battery saver warning. STOPSHIP: Remove it eventually. -->
     <string name="battery_saver_warning_title" translatable="false">Extreme battery saver</string>
+
+    <!-- Label for the uninstall button on the harmful app warning dialog. -->
+    <string name="harmful_app_warning_uninstall">Uninstall</string>
+    <!-- Label for the launch anyway button on the harmful app warning dialog. -->
+    <string name="harmful_app_warning_launch_anyway">Launch anyway</string>
+    <!-- Title for the harmful app warning dialog. -->
+    <string name="harmful_app_warning_title">Uninstall harmful app?</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ac3d402..f4ced58 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -462,6 +462,7 @@
   <java-symbol type="bool" name="config_useDefaultFocusHighlight" />
   <java-symbol type="array" name="config_deviceSpecificSystemServices" />
   <java-symbol type="string" name="config_deviceSpecificDevicePolicyManagerService" />
+  <java-symbol type="integer" name="config_num_physical_slots" />
 
   <java-symbol type="color" name="tab_indicator_text_v4" />
 
@@ -1721,6 +1722,7 @@
   <java-symbol type="string" name="global_action_lockdown" />
   <java-symbol type="string" name="global_action_voice_assist" />
   <java-symbol type="string" name="global_action_assist" />
+  <java-symbol type="string" name="global_action_screenshot" />
   <java-symbol type="string" name="invalidPuk" />
   <java-symbol type="string" name="lockscreen_carrier_default" />
   <java-symbol type="style" name="Animation.LockScreen" />
@@ -1918,6 +1920,8 @@
   <java-symbol type="string" name="wifi_wakeup_onboarding_title" />
   <java-symbol type="string" name="wifi_wakeup_onboarding_subtext" />
   <java-symbol type="string" name="wifi_wakeup_onboarding_action_disable" />
+  <java-symbol type="string" name="wifi_wakeup_enabled_title" />
+  <java-symbol type="string" name="wifi_wakeup_enabled_content" />
   <java-symbol type="string" name="accessibility_binding_label" />
   <java-symbol type="string" name="adb_active_notification_message" />
   <java-symbol type="string" name="adb_active_notification_title" />
@@ -2434,9 +2438,6 @@
   <!-- Cascading submenus -->
   <java-symbol type="dimen" name="cascading_menus_min_smallest_width" />
 
-  <!-- From SignalStrength -->
-  <java-symbol type="array" name="config_lteDbmThresholds" />
-
   <java-symbol type="string" name="android_system_label" />
   <java-symbol type="string" name="system_error_wipe_data" />
   <java-symbol type="string" name="system_error_manufacturer" />
@@ -2889,8 +2890,9 @@
 
   <java-symbol type="bool" name="config_permissionReviewRequired" />
 
-
+  <!-- Global actions icons -->
   <java-symbol type="drawable" name="ic_restart" />
+  <java-symbol type="drawable" name="ic_screenshot" />
 
   <java-symbol type="drawable" name="emergency_icon" />
 
@@ -3201,6 +3203,7 @@
 
   <java-symbol type="string" name="global_action_logout" />
   <java-symbol type="string" name="config_mainBuiltInDisplayCutout" />
+  <java-symbol type="bool" name="config_fillMainBuiltInDisplayCutout" />
   <java-symbol type="drawable" name="ic_logout" />
 
   <java-symbol type="array" name="config_autoBrightnessDisplayValuesNits" />
@@ -3208,4 +3211,8 @@
   <java-symbol type="array" name="config_screenBrightnessNits" />
 
   <java-symbol type="string" name="shortcut_disabled_reason_unknown" />
+
+  <java-symbol type="string" name="harmful_app_warning_uninstall" />
+  <java-symbol type="string" name="harmful_app_warning_launch_anyway" />
+  <java-symbol type="string" name="harmful_app_warning_title" />
 </resources>
diff --git a/core/tests/coretests/res/layout/activity_text_view.xml b/core/tests/coretests/res/layout/activity_text_view.xml
index e795c10..dca1656 100644
--- a/core/tests/coretests/res/layout/activity_text_view.xml
+++ b/core/tests/coretests/res/layout/activity_text_view.xml
@@ -25,4 +25,9 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />
 
+    <TextView
+        android:id="@+id/nonselectable_textview"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
 </LinearLayout>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index b51c677..9ab7544 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -18,11 +18,13 @@
 
 import static com.android.internal.util.NotificationColorUtil.satisfiesTextContrast;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.BitmapFactory;
@@ -40,6 +42,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.function.Consumer;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class NotificationTest {
@@ -281,6 +285,40 @@
         assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
     }
 
+    @Test
+    public void action_builder_hasDefault() {
+        Notification.Action action = makeNotificationAction(null);
+        assertEquals(Notification.Action.SEMANTIC_ACTION_NONE, action.getSemanticAction());
+    }
+
+    @Test
+    public void action_builder_setSemanticAction() {
+        Notification.Action action = makeNotificationAction(
+                builder -> builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_REPLY));
+        assertEquals(Notification.Action.SEMANTIC_ACTION_REPLY, action.getSemanticAction());
+    }
+
+    @Test
+    public void action_parcel() {
+        Notification.Action action = writeAndReadParcelable(
+                makeNotificationAction(builder -> {
+                    builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_ARCHIVE);
+                    builder.setAllowGeneratedReplies(true);
+                }));
+
+        assertEquals(Notification.Action.SEMANTIC_ACTION_ARCHIVE, action.getSemanticAction());
+        assertTrue(action.getAllowGeneratedReplies());
+    }
+
+    @Test
+    public void action_clone() {
+        Notification.Action action = makeNotificationAction(
+                builder -> builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_DELETE));
+        assertEquals(
+                Notification.Action.SEMANTIC_ACTION_DELETE,
+                action.clone().getSemanticAction());
+    }
+
     private Notification.Builder getMediaNotification() {
         MediaSession session = new MediaSession(mContext, "test");
         return new Notification.Builder(mContext, "color")
@@ -300,4 +338,18 @@
         p.setDataPosition(0);
         return p.readParcelable(/* classLoader */ null);
     }
+
+    /**
+     * Creates a Notification.Action by mocking initial dependencies and then applying
+     * transformations if they're defined.
+     */
+    private Notification.Action makeNotificationAction(
+            @Nullable Consumer<Notification.Action.Builder> transformation) {
+        Notification.Action.Builder actionBuilder =
+                new Notification.Action.Builder(null, "Test Title", null);
+        if (transformation != null) {
+            transformation.accept(actionBuilder);
+        }
+        return actionBuilder.build();
+    }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index c19a343..aefc47e 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -42,8 +42,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-// TODO: b/70616950
-//@Presubmit
+@Presubmit
 public class ObjectPoolTests {
 
     // 1. Check if two obtained objects from pool are not the same.
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index 53f4f3a..267267e 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -146,14 +146,14 @@
     }
 
     private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename,
-            boolean isPlatformReleased, int expectedTargetSdk) {
+            boolean isPlatformReleased, int expectedTargetSdk, boolean forceCurrentDev) {
         final String[] outError = new String[1];
         final int result = PackageParser.computeTargetSdkVersion(
                 targetSdkVersion,
                 targetSdkCodename,
-                PLATFORM_VERSION,
                 isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE,
-                outError);
+                outError,
+                forceCurrentDev);
 
         assertEquals(result, expectedTargetSdk);
 
@@ -169,34 +169,45 @@
         // Do allow older release targetSdkVersion on pre-release platform.
         // APP: Released API 10
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION,
+                false /* forceCurrentDev */);
 
         // Do allow same release targetSdkVersion on pre-release platform.
         // APP: Released API 20
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION,
+                false /* forceCurrentDev */);
 
         // Do allow newer release targetSdkVersion on pre-release platform.
         // APP: Released API 30
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, NEWER_VERSION);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, NEWER_VERSION,
+                false /* forceCurrentDev */);
 
         // Don't allow older pre-release targetSdkVersion on pre-release platform.
         // APP: Pre-release API 10
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1,
+                false /* forceCurrentDev */);
 
         // Do allow same pre-release targetSdkVersion on pre-release platform,
         // but overwrite the specified version with CUR_DEVELOPMENT.
         // APP: Pre-release API 20
         // DEV: Pre-release API 20
         verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
-                Build.VERSION_CODES.CUR_DEVELOPMENT);
+                Build.VERSION_CODES.CUR_DEVELOPMENT, false /* forceCurrentDev */);
 
         // Don't allow newer pre-release targetSdkVersion on pre-release platform.
         // APP: Pre-release API 30
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1,
+                false /* forceCurrentDev */);
+
+        // Force newer pre-release targetSdkVersion to current pre-release platform.
+        // APP: Pre-release API 30
+        // DEV: Pre-release API 20
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false,
+                Build.VERSION_CODES.CUR_DEVELOPMENT, true /* forceCurrentDev */);
     }
 
     @Test
@@ -204,32 +215,38 @@
         // Do allow older release targetSdkVersion on released platform.
         // APP: Released API 10
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION,
+                false /* forceCurrentDev */);
 
         // Do allow same release targetSdkVersion on released platform.
         // APP: Released API 20
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION,
+                false /* forceCurrentDev */);
 
         // Do allow newer release targetSdkVersion on released platform.
         // APP: Released API 30
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, NEWER_VERSION);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, NEWER_VERSION,
+                false /* forceCurrentDev */);
 
         // Don't allow older pre-release targetSdkVersion on released platform.
         // APP: Pre-release API 10
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1,
+                false /* forceCurrentDev */);
 
         // Don't allow same pre-release targetSdkVersion on released platform.
         // APP: Pre-release API 20
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1,
+                false /* forceCurrentDev */);
 
         // Don't allow newer pre-release targetSdkVersion on released platform.
         // APP: Pre-release API 30
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1,
+                false /* forceCurrentDev */);
     }
 
     /**
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 68789f3..410bee0 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -350,6 +350,7 @@
                     Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
                     Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES,
                     Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE,
+                    Settings.Global.SYS_VDSO,
                     Settings.Global.TCP_DEFAULT_INIT_RWND,
                     Settings.Global.TETHER_DUN_APN,
                     Settings.Global.TETHER_DUN_REQUIRED,
@@ -421,7 +422,8 @@
                     Settings.Global.WTF_IS_FATAL,
                     Settings.Global.ZEN_MODE,
                     Settings.Global.ZEN_MODE_CONFIG_ETAG,
-                    Settings.Global.ZEN_MODE_RINGER_LEVEL);
+                    Settings.Global.ZEN_MODE_RINGER_LEVEL,
+                    Settings.Global.ZRAM_ENABLED);
 
     private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
              newHashSet(
@@ -513,6 +515,7 @@
                  Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
                  Settings.Secure.SETTINGS_CLASSNAME,
                  Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, // candidate?
+                 Settings.Secure.SHOW_ROTATION_SUGGESTIONS,
                  Settings.Secure.SKIP_FIRST_USE_HINTS, // candidate?
                  Settings.Secure.SMS_DEFAULT_APPLICATION,
                  Settings.Secure.TRUST_AGENTS_INITIALIZED,
diff --git a/core/tests/coretests/src/android/text/MeasuredTextTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
similarity index 87%
rename from core/tests/coretests/src/android/text/MeasuredTextTest.java
rename to core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index ddef0c6..5d33397 100644
--- a/core/tests/coretests/src/android/text/MeasuredTextTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -31,7 +31,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class MeasuredTextTest {
+public class MeasuredParagraphTest {
     private static final TextDirectionHeuristic LTR = TextDirectionHeuristics.LTR;
     private static final TextDirectionHeuristic RTL = TextDirectionHeuristics.RTL;
 
@@ -60,9 +60,9 @@
 
     @Test
     public void buildForBidi() {
-        MeasuredText mt = null;
+        MeasuredParagraph mt = null;
 
-        mt = MeasuredText.buildForBidi("XXX", 0, 3, LTR, null);
+        mt = MeasuredParagraph.buildForBidi("XXX", 0, 3, LTR, null);
         assertNotNull(mt);
         assertNotNull(mt.getChars());
         assertEquals("XXX", charsToString(mt.getChars()));
@@ -75,7 +75,7 @@
         assertEquals(0, mt.getNativePtr());
 
         // Recycle it
-        MeasuredText mt2 = MeasuredText.buildForBidi("_VVV_", 1, 4, RTL, mt);
+        MeasuredParagraph mt2 = MeasuredParagraph.buildForBidi("_VVV_", 1, 4, RTL, mt);
         assertEquals(mt2, mt);
         assertNotNull(mt2.getChars());
         assertEquals("VVV", charsToString(mt.getChars()));
@@ -91,9 +91,9 @@
 
     @Test
     public void buildForMeasurement() {
-        MeasuredText mt = null;
+        MeasuredParagraph mt = null;
 
-        mt = MeasuredText.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null);
+        mt = MeasuredParagraph.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null);
         assertNotNull(mt);
         assertNotNull(mt.getChars());
         assertEquals("XXX", charsToString(mt.getChars()));
@@ -109,7 +109,8 @@
         assertEquals(0, mt.getNativePtr());
 
         // Recycle it
-        MeasuredText mt2 = MeasuredText.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt);
+        MeasuredParagraph mt2 =
+                MeasuredParagraph.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt);
         assertEquals(mt2, mt);
         assertNotNull(mt2.getChars());
         assertEquals("VVV", charsToString(mt.getChars()));
@@ -129,9 +130,9 @@
 
     @Test
     public void buildForStaticLayout() {
-        MeasuredText mt = null;
+        MeasuredParagraph mt = null;
 
-        mt = MeasuredText.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null);
+        mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null);
         assertNotNull(mt);
         assertNotNull(mt.getChars());
         assertEquals("XXX", charsToString(mt.getChars()));
@@ -145,7 +146,8 @@
         assertNotEquals(0, mt.getNativePtr());
 
         // Recycle it
-        MeasuredText mt2 = MeasuredText.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt);
+        MeasuredParagraph mt2 =
+                MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt);
         assertEquals(mt2, mt);
         assertNotNull(mt2.getChars());
         assertEquals("VVV", charsToString(mt.getChars()));
@@ -163,6 +165,6 @@
 
     @Test
     public void testFor70146381() {
-        MeasuredText.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null);
+        MeasuredParagraph.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null);
     }
 }
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
new file mode 100644
index 0000000..9ee7fac
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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 android.view.textclassifier;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassificationTest {
+
+    public BitmapDrawable generateTestDrawable(int width, int height, int colorValue) {
+        final int numPixels = width * height;
+        final int[] colors = new int[numPixels];
+        for (int i = 0; i < numPixels; ++i) {
+            colors[i] = colorValue;
+        }
+        final Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
+        final BitmapDrawable drawable = new BitmapDrawable(null, bitmap);
+        drawable.setTargetDensity(bitmap.getDensity());
+        return drawable;
+    }
+
+    @Test
+    public void testParcel() {
+        final String text = "text";
+        final BitmapDrawable primaryIcon = generateTestDrawable(16, 16, Color.RED);
+        final String primaryLabel = "primarylabel";
+        final Intent primaryIntent = new Intent("primaryintentaction");
+        final View.OnClickListener primaryOnClick = v -> { };
+        final BitmapDrawable secondaryIcon0 = generateTestDrawable(32, 288, Color.GREEN);
+        final String secondaryLabel0 = "secondarylabel0";
+        final Intent secondaryIntent0 = new Intent("secondaryintentaction0");
+        final BitmapDrawable secondaryIcon1 = generateTestDrawable(576, 288, Color.BLUE);
+        final String secondaryLabel1 = "secondaryLabel1";
+        final Intent secondaryIntent1 = null;
+        final BitmapDrawable secondaryIcon2 = null;
+        final String secondaryLabel2 = null;
+        final Intent secondaryIntent2 = new Intent("secondaryintentaction2");
+        final ColorDrawable secondaryIcon3 = new ColorDrawable(Color.CYAN);
+        final String secondaryLabel3 = null;
+        final Intent secondaryIntent3 = null;
+        final String signature = "signature";
+        final TextClassification reference = new TextClassification.Builder()
+                .setText(text)
+                .setPrimaryAction(primaryIntent, primaryLabel, primaryIcon)
+                .setOnClickListener(primaryOnClick)
+                .addSecondaryAction(null, null, null)  // ignored
+                .addSecondaryAction(secondaryIntent0, secondaryLabel0, secondaryIcon0)
+                .addSecondaryAction(secondaryIntent1, secondaryLabel1, secondaryIcon1)
+                .addSecondaryAction(secondaryIntent2, secondaryLabel2, secondaryIcon2)
+                .addSecondaryAction(secondaryIntent3, secondaryLabel3, secondaryIcon3)
+                .setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f)
+                .setEntityType(TextClassifier.TYPE_PHONE, 0.7f)
+                .setSignature(signature)
+                .build();
+
+        // Parcel and unparcel using ParcelableWrapper.
+        final TextClassification.ParcelableWrapper parcelableReference = new TextClassification
+                .ParcelableWrapper(reference);
+        final Parcel parcel = Parcel.obtain();
+        parcelableReference.writeToParcel(parcel, parcelableReference.describeContents());
+        parcel.setDataPosition(0);
+        final TextClassification result =
+                TextClassification.ParcelableWrapper.CREATOR.createFromParcel(
+                        parcel).getTextClassification();
+
+        assertEquals(text, result.getText());
+        assertEquals(signature, result.getSignature());
+        assertEquals(4, result.getSecondaryActionsCount());
+
+        // Primary action (re-use existing icon).
+        final Bitmap resPrimaryIcon = ((BitmapDrawable) result.getIcon()).getBitmap();
+        assertEquals(primaryIcon.getBitmap().getPixel(0, 0), resPrimaryIcon.getPixel(0, 0));
+        assertEquals(16, resPrimaryIcon.getWidth());
+        assertEquals(16, resPrimaryIcon.getHeight());
+        assertEquals(primaryLabel, result.getLabel());
+        assertEquals(primaryIntent.getAction(), result.getIntent().getAction());
+        assertEquals(null, result.getOnClickListener());  // Non-parcelable.
+
+        // Secondary action 0 (scale with  height limit).
+        final Bitmap resSecondaryIcon0 = ((BitmapDrawable) result.getSecondaryIcon(0)).getBitmap();
+        assertEquals(secondaryIcon0.getBitmap().getPixel(0, 0), resSecondaryIcon0.getPixel(0, 0));
+        assertEquals(16, resSecondaryIcon0.getWidth());
+        assertEquals(144, resSecondaryIcon0.getHeight());
+        assertEquals(secondaryLabel0, result.getSecondaryLabel(0));
+        assertEquals(secondaryIntent0.getAction(), result.getSecondaryIntent(0).getAction());
+
+        // Secondary action 1 (scale with width limit).
+        final Bitmap resSecondaryIcon1 = ((BitmapDrawable) result.getSecondaryIcon(1)).getBitmap();
+        assertEquals(secondaryIcon1.getBitmap().getPixel(0, 0), resSecondaryIcon1.getPixel(0, 0));
+        assertEquals(144, resSecondaryIcon1.getWidth());
+        assertEquals(72, resSecondaryIcon1.getHeight());
+        assertEquals(secondaryLabel1, result.getSecondaryLabel(1));
+        assertEquals(null, result.getSecondaryIntent(1));
+
+        // Secondary action 2 (no icon).
+        assertEquals(null, result.getSecondaryIcon(2));
+        assertEquals(null, result.getSecondaryLabel(2));
+        assertEquals(secondaryIntent2.getAction(), result.getSecondaryIntent(2).getAction());
+
+        // Secondary action 3 (convert non-bitmap drawable with negative size).
+        final Bitmap resSecondaryIcon3 = ((BitmapDrawable) result.getSecondaryIcon(3)).getBitmap();
+        assertEquals(secondaryIcon3.getColor(), resSecondaryIcon3.getPixel(0, 0));
+        assertEquals(1, resSecondaryIcon3.getWidth());
+        assertEquals(1, resSecondaryIcon3.getHeight());
+        assertEquals(null, result.getSecondaryLabel(3));
+        assertEquals(null, result.getSecondaryIntent(3));
+
+        // Entities.
+        assertEquals(2, result.getEntityCount());
+        assertEquals(TextClassifier.TYPE_PHONE, result.getEntity(0));
+        assertEquals(TextClassifier.TYPE_ADDRESS, result.getEntity(1));
+        assertEquals(0.7f, result.getConfidenceScore(TextClassifier.TYPE_PHONE), 1e-7f);
+        assertEquals(0.3f, result.getConfidenceScore(TextClassifier.TYPE_ADDRESS), 1e-7f);
+    }
+
+    @Test
+    public void testParcelOptions() {
+        TextClassification.Options reference = new TextClassification.Options();
+        reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY));
+
+        // Parcel and unparcel.
+        final Parcel parcel = Parcel.obtain();
+        reference.writeToParcel(parcel, reference.describeContents());
+        parcel.setDataPosition(0);
+        TextClassification.Options result = TextClassification.Options.CREATOR.createFromParcel(
+                parcel);
+
+        assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags());
+    }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
new file mode 100644
index 0000000..a82542c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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 android.view.textclassifier;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextLinksTest {
+
+    private TextClassificationManager mTcm;
+    private TextClassifier mClassifier;
+
+    @Before
+    public void setup() {
+        mTcm = InstrumentationRegistry.getTargetContext()
+                .getSystemService(TextClassificationManager.class);
+        mTcm.setTextClassifier(null);
+        mClassifier = mTcm.getTextClassifier();
+    }
+
+    private Map<String, Float> getEntityScores(float address, float phone, float other) {
+        final Map<String, Float> result = new ArrayMap<>();
+        if (address > 0.f) {
+            result.put(TextClassifier.TYPE_ADDRESS, address);
+        }
+        if (phone > 0.f) {
+            result.put(TextClassifier.TYPE_PHONE, phone);
+        }
+        if (other > 0.f) {
+            result.put(TextClassifier.TYPE_OTHER, other);
+        }
+        return result;
+    }
+
+    @Test
+    public void testParcel() {
+        final String fullText = "this is just a test";
+        final TextLinks reference = new TextLinks.Builder(fullText)
+                .addLink(new TextLinks.TextLink(fullText, 0, 4, getEntityScores(0.f, 0.f, 1.f)))
+                .addLink(new TextLinks.TextLink(fullText, 5, 12, getEntityScores(.8f, .1f, .5f)))
+                .build();
+
+        // Parcel and unparcel.
+        final Parcel parcel = Parcel.obtain();
+        reference.writeToParcel(parcel, reference.describeContents());
+        parcel.setDataPosition(0);
+        final TextLinks result = TextLinks.CREATOR.createFromParcel(parcel);
+        final List<TextLinks.TextLink> resultList = new ArrayList<>(result.getLinks());
+
+        assertEquals(2, resultList.size());
+        assertEquals(0, resultList.get(0).getStart());
+        assertEquals(4, resultList.get(0).getEnd());
+        assertEquals(1, resultList.get(0).getEntityCount());
+        assertEquals(TextClassifier.TYPE_OTHER, resultList.get(0).getEntity(0));
+        assertEquals(1.f, resultList.get(0).getConfidenceScore(TextClassifier.TYPE_OTHER), 1e-7f);
+        assertEquals(5, resultList.get(1).getStart());
+        assertEquals(12, resultList.get(1).getEnd());
+        assertEquals(3, resultList.get(1).getEntityCount());
+        assertEquals(TextClassifier.TYPE_ADDRESS, resultList.get(1).getEntity(0));
+        assertEquals(TextClassifier.TYPE_OTHER, resultList.get(1).getEntity(1));
+        assertEquals(TextClassifier.TYPE_PHONE, resultList.get(1).getEntity(2));
+        assertEquals(.8f, resultList.get(1).getConfidenceScore(TextClassifier.TYPE_ADDRESS), 1e-7f);
+        assertEquals(.5f, resultList.get(1).getConfidenceScore(TextClassifier.TYPE_OTHER), 1e-7f);
+        assertEquals(.1f, resultList.get(1).getConfidenceScore(TextClassifier.TYPE_PHONE), 1e-7f);
+    }
+
+    @Test
+    public void testParcelOptions() {
+        TextClassifier.EntityConfig entityConfig = new TextClassifier.EntityConfig(
+                TextClassifier.ENTITY_PRESET_NONE);
+        entityConfig.includeEntities("a", "b", "c");
+        entityConfig.excludeEntities("b");
+        TextLinks.Options reference = new TextLinks.Options();
+        reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY));
+        reference.setEntityConfig(entityConfig);
+
+        // Parcel and unparcel.
+        final Parcel parcel = Parcel.obtain();
+        reference.writeToParcel(parcel, reference.describeContents());
+        parcel.setDataPosition(0);
+        TextLinks.Options result = TextLinks.Options.CREATOR.createFromParcel(parcel);
+
+        assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags());
+        assertEquals(Arrays.asList("a", "c"), result.getEntityConfig().getEntities(mClassifier));
+    }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java
new file mode 100644
index 0000000..e920236
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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 android.view.textclassifier;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextSelectionTest {
+
+    @Test
+    public void testParcel() {
+        final int startIndex = 13;
+        final int endIndex = 37;
+        final String signature = "signature";
+        final TextSelection reference = new TextSelection.Builder(startIndex, endIndex)
+                .setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f)
+                .setEntityType(TextClassifier.TYPE_PHONE, 0.7f)
+                .setEntityType(TextClassifier.TYPE_URL, 0.1f)
+                .setSignature(signature)
+                .build();
+
+        // Parcel and unparcel using ParcelableWrapper.
+        final TextSelection.ParcelableWrapper parcelableReference = new TextSelection
+                .ParcelableWrapper(reference);
+        final Parcel parcel = Parcel.obtain();
+        parcelableReference.writeToParcel(parcel, parcelableReference.describeContents());
+        parcel.setDataPosition(0);
+        final TextSelection result =
+                TextSelection.ParcelableWrapper.CREATOR.createFromParcel(
+                        parcel).getTextSelection();
+
+        assertEquals(startIndex, result.getSelectionStartIndex());
+        assertEquals(endIndex, result.getSelectionEndIndex());
+        assertEquals(signature, result.getSignature());
+
+        assertEquals(3, result.getEntityCount());
+        assertEquals(TextClassifier.TYPE_PHONE, result.getEntity(0));
+        assertEquals(TextClassifier.TYPE_ADDRESS, result.getEntity(1));
+        assertEquals(TextClassifier.TYPE_URL, result.getEntity(2));
+        assertEquals(0.7f, result.getConfidenceScore(TextClassifier.TYPE_PHONE), 1e-7f);
+        assertEquals(0.3f, result.getConfidenceScore(TextClassifier.TYPE_ADDRESS), 1e-7f);
+        assertEquals(0.1f, result.getConfidenceScore(TextClassifier.TYPE_URL), 1e-7f);
+    }
+
+    @Test
+    public void testParcelOptions() {
+        TextSelection.Options reference = new TextSelection.Options();
+        reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY));
+        reference.setDarkLaunchAllowed(true);
+
+        // Parcel and unparcel.
+        final Parcel parcel = Parcel.obtain();
+        reference.writeToParcel(parcel, reference.describeContents());
+        parcel.setDataPosition(0);
+        TextSelection.Options result = TextSelection.Options.CREATOR.createFromParcel(parcel);
+
+        assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags());
+        assertTrue(result.isDarkLaunchAllowed());
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 1a654f4..bbca12f 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -311,11 +311,20 @@
 
     @Test
     public void testToolbarAppearsAfterLinkClicked() throws Throwable {
+        runToolbarAppearsAfterLinkClickedTest(R.id.textview);
+    }
+
+    @Test
+    public void testToolbarAppearsAfterLinkClickedNonselectable() throws Throwable {
+        runToolbarAppearsAfterLinkClickedTest(R.id.nonselectable_textview);
+    }
+
+    private void runToolbarAppearsAfterLinkClickedTest(int id) throws Throwable {
+        TextView textView = mActivity.findViewById(id);
         useSystemDefaultTextClassifier();
         TextClassificationManager textClassificationManager =
                 mActivity.getSystemService(TextClassificationManager.class);
         TextClassifier textClassifier = textClassificationManager.getTextClassifier();
-        final TextView textView = mActivity.findViewById(R.id.textview);
         SpannableString content = new SpannableString("Call me at +19148277737");
         TextLinks links = textClassifier.generateLinks(content);
         links.apply(content, null);
@@ -331,7 +340,7 @@
 
         TextLinks.TextLink textLink = links.getLinks().iterator().next();
         int position = (textLink.getStart() + textLink.getEnd()) / 2;
-        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position));
+        onView(withId(id)).perform(clickOnTextAtIndex(position));
         sleepForFloatingToolbarPopup();
         assertFloatingToolbarIsDisplayed();
     }
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index d2c855b..f169f22 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -168,6 +168,8 @@
     <assign-permission name="android.permission.ACCESS_LOWPAN_STATE" uid="lowpan" />
     <assign-permission name="android.permission.MANAGE_LOWPAN_INTERFACES" uid="lowpan" />
 
+    <assign-permission name="android.permission.STATSCOMPANION" uid="statsd" />
+
     <!-- This is a list of all the libraries available for application
          code to link against. -->
 
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index dea194e..4571553 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -16,17 +16,12 @@
 
 package android.graphics.drawable;
 
-import android.animation.Animator;
-import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.graphics.Canvas;
-import android.graphics.CanvasProperty;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.util.FloatProperty;
-import android.view.DisplayListCanvas;
-import android.view.RenderNodeAnimator;
 import android.view.animation.LinearInterpolator;
 
 /**
@@ -78,8 +73,8 @@
 
     private void onStateChanged(boolean animateChanged) {
         float newOpacity = 0.0f;
-        if (mHovered) newOpacity += 1.0f;
-        if (mFocused) newOpacity += 1.0f;
+        if (mHovered) newOpacity += .25f;
+        if (mFocused) newOpacity += .75f;
         if (mAnimator != null) {
             mAnimator.cancel();
             mAnimator = null;
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 734cff5..b883656 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -264,8 +264,8 @@
         }
 
         setRippleActive(enabled && pressed);
-
         setBackgroundActive(hovered, focused);
+
         return changed;
     }
 
@@ -879,22 +879,18 @@
         // Grab the color for the current state and cut the alpha channel in
         // half so that the ripple and background together yield full alpha.
         final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
-        final int halfAlpha = (Color.alpha(color) / 2) << 24;
         final Paint p = mRipplePaint;
 
         if (mMaskColorFilter != null) {
             // The ripple timing depends on the paint's alpha value, so we need
             // to push just the alpha channel into the paint and let the filter
             // handle the full-alpha color.
-            final int fullAlphaColor = color | (0xFF << 24);
-            mMaskColorFilter.setColor(fullAlphaColor);
-
-            p.setColor(halfAlpha);
+            mMaskColorFilter.setColor(color | 0xFF000000);
+            p.setColor(color & 0xFF000000);
             p.setColorFilter(mMaskColorFilter);
             p.setShader(mMaskShader);
         } else {
-            final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
-            p.setColor(halfAlphaColor);
+            p.setColor(color);
             p.setColorFilter(null);
             p.setShader(null);
         }
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 13e1ebe..2e08670 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -765,7 +765,8 @@
 
     for (size_t i = start; i < end; i++) {
         glyphs[i - start] = layout.getGlyphId(i);
-        float x = hOffset + layout.getX(i);
+        float halfWidth = layout.getCharAdvance(i) * 0.5f;
+        float x = hOffset + layout.getX(i) + halfWidth;
         float y = vOffset + layout.getY(i);
 
         SkPoint pos;
@@ -776,8 +777,8 @@
         }
         xform[i - start].fSCos = tan.x();
         xform[i - start].fSSin = tan.y();
-        xform[i - start].fTx = pos.x() - tan.y() * y;
-        xform[i - start].fTy = pos.y() + tan.x() * y;
+        xform[i - start].fTx = pos.x() - tan.y() * y - halfWidth * tan.x();
+        xform[i - start].fTy = pos.y() + tan.x() * y - halfWidth * tan.y();
     }
 
     this->asSkCanvas()->drawTextRSXform(glyphs, sizeof(uint16_t) * N, xform, nullptr, paint);
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanReadback.h b/libs/hwui/pipeline/skia/SkiaVulkanReadback.h
new file mode 100644
index 0000000..65b89d6
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaVulkanReadback.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Readback.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaVulkanReadback : public Readback {
+public:
+    SkiaVulkanReadback(renderthread::RenderThread& thread) : Readback(thread) {}
+
+    virtual CopyResult copySurfaceInto(Surface& surface, const Rect& srcRect,
+            SkBitmap* bitmap) override {
+        //TODO: implement Vulkan readback.
+        return CopyResult::UnknownError;
+    }
+
+    virtual CopyResult copyGraphicBufferInto(GraphicBuffer* graphicBuffer,
+            SkBitmap* bitmap) override {
+        //TODO: implement Vulkan readback.
+        return CopyResult::UnknownError;
+    }
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 79dc09f..8e0546b 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -24,6 +24,7 @@
 #include "hwui/Bitmap.h"
 #include "pipeline/skia/SkiaOpenGLPipeline.h"
 #include "pipeline/skia/SkiaOpenGLReadback.h"
+#include "pipeline/skia/SkiaVulkanReadback.h"
 #include "pipeline/skia/SkiaVulkanPipeline.h"
 #include "renderstate/RenderState.h"
 #include "renderthread/OpenGLPipeline.h"
@@ -158,12 +159,11 @@
                 mReadback = new OpenGLReadbackImpl(*this);
                 break;
             case RenderPipelineType::SkiaGL:
-            case RenderPipelineType::SkiaVulkan:
-                // It works to use the OpenGL pipeline for Vulkan but this is not
-                // ideal as it causes us to create an OpenGL context in addition
-                // to the Vulkan one.
                 mReadback = new skiapipeline::SkiaOpenGLReadback(*this);
                 break;
+            case RenderPipelineType::SkiaVulkan:
+                mReadback = new skiapipeline::SkiaVulkanReadback(*this);
+                break;
             default:
                 LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
                 break;
diff --git a/libs/services/Android.bp b/libs/services/Android.bp
new file mode 100644
index 0000000..e5e865f
--- /dev/null
+++ b/libs/services/Android.bp
@@ -0,0 +1,47 @@
+// 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.
+
+// Provides C++ wrappers for system services.
+
+cc_library_shared {
+    name: "libservices",
+    srcs: [
+        ":IDropBoxManagerService.aidl",
+        "src/os/DropBoxManager.cpp",
+        "src/os/StatsLogEventWrapper.cpp",
+    ],
+
+    shared_libs: [
+        "libbinder",
+        "liblog",
+        "libcutils",
+        "libutils",
+    ],
+    header_libs: [
+	"libbase_headers",
+    ],
+    aidl: {
+        include_dirs: ["frameworks/base/core/java/"],
+    },
+
+    export_include_dirs: ["include"],
+    export_header_lib_headers: ["libbase_headers"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+}
diff --git a/libs/services/Android.mk b/libs/services/Android.mk
deleted file mode 100644
index d72059a..0000000
--- a/libs/services/Android.mk
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (C) 2010 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-# Provides C++ wrappers for system services.
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libservices
-LOCAL_SRC_FILES := \
-    ../../core/java/com/android/internal/os/IDropBoxManagerService.aidl \
-    src/os/DropBoxManager.cpp \
-    src/os/StatsLogEventWrapper.cpp
-
-LOCAL_AIDL_INCLUDES := \
-    $(LOCAL_PATH)/../../core/java
-LOCAL_C_INCLUDES := \
-    system/core/include
-LOCAL_SHARED_LIBRARIES := \
-    libbinder \
-    liblog \
-    libcutils \
-    libutils
-
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-include $(BUILD_SHARED_LIBRARY)
-
-
diff --git a/native/android/net.c b/native/android/net.c
index de4b90c..60296a7 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -27,7 +27,7 @@
     static const uint32_t k32BitMask = 0xffffffff;
     // This value MUST be kept in sync with the corresponding value in
     // the android.net.Network#getNetworkHandle() implementation.
-    static const uint32_t kHandleMagic = 0xfacade;
+    static const uint32_t kHandleMagic = 0xcafed00d;
 
     // Check for minimum acceptable version of the API in the low bits.
     if (handle != NETWORK_UNSPECIFIED &&
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index 8b00ed0..faa10cc 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -23,6 +23,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.inputmethod.InputMethodManager;
+
 import com.android.printspooler.R;
 
 /**
@@ -410,7 +411,7 @@
 
             mPrintButton.offsetTopAndBottom(dy);
 
-            mDraggableContent.notifySubtreeAccessibilityStateChangedIfNeeded();
+            mDraggableContent.notifyAccessibilitySubtreeChanged();
 
             onDragProgress(progress);
         }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 48de1c9..3698132 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1119,6 +1119,9 @@
         dumpSetting(s, p,
                 Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
                 GlobalSettingsProto.NOTIFICATION_SNOOZE_OPTIONS);
+        dumpSetting(s, p,
+                    Settings.Global.ZRAM_ENABLED,
+                    GlobalSettingsProto.ZRAM_ENABLED);
     }
 
     /** Dump a single {@link SettingsState.Setting} to a proto buf */
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b3d6357..0f43db0 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -134,6 +134,7 @@
     <!-- Permission needed to access privileged VR APIs -->
     <uses-permission android:name="android.permission.RESTRICTED_VR_ACCESS" />
     <uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE" />
+    <uses-permission android:name="android.permission.SET_HARMFUL_APP_WARNINGS" />
 
     <application android:label="@string/app_label"
                  android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 73d03c6..600f0dc 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -616,7 +616,7 @@
         final IWindowManager wm = IWindowManager.Stub
                 .asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
         try {
-            wm.dismissKeyguard(null);
+            wm.dismissKeyguard(null, null);
         } catch (Exception e) {
             // ignore it
         }
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index c97cfc4..347cf1c 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -34,7 +34,6 @@
         android:orientation="vertical">
         <RelativeLayout
             android:id="@+id/keyguard_clock_container"
-            android:animateLayoutChanges="true"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_gravity="center_horizontal|top">
diff --git a/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml b/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml
new file mode 100644
index 0000000..255e377
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+                 xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector android:height="24dp"
+                android:width="24dp"
+                android:viewportHeight="102"
+                android:viewportWidth="102"
+                android:tint="?attr/singleToneColor">
+            <group android:name="_R_G">
+                <group android:name="_R_G_L_0_G" android:translateX="53.086" android:translateY="48.907000000000004" android:pivotX="-2.083" android:pivotY="2.083" android:rotation="90">
+                    <group android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0" android:rotation="100.1" android:scaleX="0.7979999999999999" android:scaleY="0.7979999999999999">
+                        <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M17.15 -37.84 C30.19,-31.91 39.52,-19.62 41.9,-4.86 C42.15,-3.39 43.45,-2.31 44.95,-2.31 C46.88,-2.31 48.34,-4.06 48.05,-5.94 C44.37,-27.64 27.64,-45.91 0.84,-48.09 C-1.08,-48.25 -2.17,-45.91 -0.83,-44.53 C-0.83,-44.53 9.87,-33.83 9.87,-33.83 C10.67,-33.04 11.92,-33.04 12.76,-33.79 C12.76,-33.79 17.15,-37.84 17.15,-37.84c "/>
+                    </group>
+                    <group android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0" android:rotation="87.2" android:scaleX="0.77" android:scaleY="0.77">
+                        <path android:name="_R_G_L_0_G_D_1_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-21.32 42.01 C-34.36,36.07 -43.68,23.78 -46.07,9.02 C-46.33,7.55 -47.62,6.47 -49.12,6.47 C-51.04,6.47 -52.51,8.23 -52.21,10.11 C-48.53,31.81 -31.81,50.08 -5.01,52.25 C-3.09,52.42 -2,50.08 -3.34,48.7 C-3.34,48.7 -14.04,38 -14.04,38 C-14.84,37.21 -16.11,37.19 -16.93,37.96 C-16.93,37.96 -21.32,42.01 -21.32,42.01c "/>
+                    </group>
+                    <path android:name="_R_G_L_0_G_D_2_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.77 9.4 C40.77,9.4 -9.4,-40.77 -9.4,-40.77 C-11.91,-43.28 -15.67,-43.28 -18.18,-40.77 C-18.18,-40.77 -44.94,-14.01 -44.94,-14.01 C-47.45,-11.5 -47.45,-7.74 -44.94,-5.23 C-44.94,-5.23 5.23,44.94 5.23,44.94 C7.74,47.45 11.51,47.45 14.01,44.94 C14.01,44.94 40.77,18.18 40.77,18.18 C43.28,15.67 43.28,11.91 40.77,9.4c  M3.85 34.82 C3.85,34.82 -34.4,-3.44 -34.4,-3.44 C-34.4,-3.44 -7.64,-30.19 -7.64,-30.19 C-7.64,-30.19 30.61,8.06 30.61,8.06 C30.61,8.06 3.85,34.82 3.85,34.82c "/>
+                </group>
+            </group>
+            <group android:name="time_group"/>
+        </vector>
+    </aapt:attr>
+
+    <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="rotation" android:duration="333" android:startOffset="0" android:valueFrom="100.1" android:valueTo="0" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+
+    <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="0" android:valueFrom="0.798" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="0" android:valueFrom="0.798" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+
+    <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="rotation" android:duration="333" android:startOffset="0" android:valueFrom="87.2" android:valueTo="0" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+
+    <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="0" android:valueFrom="0.77" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="0" android:valueFrom="0.77" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="rotation" android:duration="333" android:startOffset="0" android:valueFrom="90" android:valueTo="0" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/rotate_suggestion.xml b/packages/SystemUI/res/layout/rotate_suggestion.xml
new file mode 100644
index 0000000..7762950
--- /dev/null
+++ b/packages/SystemUI/res/layout/rotate_suggestion.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="@dimen/navigation_side_padding"
+    android:layout_height="match_parent"
+    android:layout_weight="0"
+    >
+    <com.android.systemui.statusbar.policy.KeyButtonView
+        android:id="@+id/rotate_suggestion"
+        android:layout_width="@dimen/navigation_extra_key_width"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="2dp"
+        android:visibility="invisible"
+        android:scaleType="centerInside"
+    />
+    <!-- TODO android:contentDescription -->
+</FrameLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a19917d..dde4dcf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -61,11 +61,26 @@
     <!-- When the battery is low, this is displayed to the user in a dialog.  The title of the low battery alert.  [CHAR LIMIT=NONE]-->
     <string name="battery_low_title">Battery is low</string>
 
+    <!-- When the battery is low and hybrid notifications are enabled, this is displayed to the user in a dialog.
+         The title of the low battery alert.  [CHAR LIMIT=NONE]-->
+    <string name="battery_low_title_hybrid">Battery is low. Turn on Battery Saver</string>
+
     <!-- A message that appears when the battery level is getting low in a dialog.  This is
-        appened to the subtitle of the low battery alert.  "percentage" is the percentage of battery
+        appended to the subtitle of the low battery alert.  "percentage" is the percentage of battery
         remaining [CHAR LIMIT=none]-->
     <string name="battery_low_percent_format"><xliff:g id="percentage">%s</xliff:g> remaining</string>
 
+    <!-- A message that appears when the battery remaining estimate is low in a dialog.  This is
+    appended to the subtitle of the low battery alert.  "percentage" is the percentage of battery
+    remaining. "time" is the amount of time remaining before the phone runs out of battery [CHAR LIMIT=none]-->
+    <string name="battery_low_percent_format_hybrid"><xliff:g id="percentage">%s</xliff:g> remaining, about <xliff:g id="time">%s</xliff:g> left based on your usage</string>
+
+    <!-- A message that appears when the battery remaining estimate is low in a dialog and insufficient
+    data was present to say it is customized to the user. This is appended to the subtitle of the
+    low battery alert.  "percentage" is the percentage of battery remaining. "time" is the amount
+     of time remaining before the phone runs out of battery [CHAR LIMIT=none]-->
+    <string name="battery_low_percent_format_hybrid_short"><xliff:g id="percentage">%s</xliff:g> remaining, about <xliff:g id="time">%s</xliff:g> left</string>
+
     <!-- Same as battery_low_percent_format, with a notice about battery saver if on. [CHAR LIMIT=none]-->
     <string name="battery_low_percent_format_saver_started"><xliff:g id="percentage">%s</xliff:g> remaining. Battery Saver is on.</string>
 
@@ -173,22 +188,25 @@
          [CHAR LIMIT=25] -->
     <string name="compat_mode_off">Stretch to fill screen</string>
 
+    <!-- Power menu item for taking a screenshot [CHAR LIMIT=20]-->
+    <string name="global_action_screenshot">Screenshot</string>
+
     <!-- Notification ticker displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=30] -->
     <string name="screenshot_saving_ticker">Saving screenshot\u2026</string>
     <!-- Notification title displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=50] -->
     <string name="screenshot_saving_title">Saving screenshot\u2026</string>
     <!-- Notification text displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=100] -->
-    <string name="screenshot_saving_text">Screenshot is being saved.</string>
+    <string name="screenshot_saving_text">Screenshot is being saved</string>
     <!-- Notification title displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=50] -->
-    <string name="screenshot_saved_title">Screenshot captured.</string>
+    <string name="screenshot_saved_title">Screenshot saved</string>
     <!-- Notification text displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=100] -->
-    <string name="screenshot_saved_text">Tap to view your screenshot.</string>
+    <string name="screenshot_saved_text">Tap to view your screenshot</string>
     <!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
-    <string name="screenshot_failed_title">Couldn\'t capture screenshot.</string>
+    <string name="screenshot_failed_title">Couldn\'t capture screenshot</string>
     <!-- Notification text displayed when we fail to save a screenshot for unknown reasons. [CHAR LIMIT=100] -->
-    <string name="screenshot_failed_to_save_unknown_text">Problem encountered while saving screenshot.</string>
+    <string name="screenshot_failed_to_save_unknown_text">Problem encountered while saving screenshot</string>
     <!-- Notification text displayed when we fail to save a screenshot. [CHAR LIMIT=100] -->
-    <string name="screenshot_failed_to_save_text">Can\'t save screenshot due to limited storage space.</string>
+    <string name="screenshot_failed_to_save_text">Can\'t save screenshot due to limited storage space</string>
     <!-- Notification text displayed when we fail to take a screenshot. [CHAR LIMIT=100] -->
     <string name="screenshot_failed_to_capture_text">Taking screenshots isn\'t allowed by the app or
         your organization</string>
@@ -2038,4 +2056,5 @@
     <string name="touch_filtered_warning">Because an app is obscuring a permission request, Settings
         can’t verify your response.</string>
 
+
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index a980413..d63ad08 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -280,7 +280,7 @@
     @Override
     public void showPromptReason(int reason) {
         if (reason != PROMPT_REASON_NONE) {
-            int promtReasonStringRes = getPromtReasonStringRes(reason);
+            int promtReasonStringRes = getPromptReasonStringRes(reason);
             if (promtReasonStringRes != 0) {
                 mSecurityMessageDisplay.setMessage(promtReasonStringRes);
             }
@@ -288,12 +288,12 @@
     }
 
     @Override
-    public void showMessage(String message, int color) {
+    public void showMessage(CharSequence message, int color) {
         mSecurityMessageDisplay.setNextMessageColor(color);
         mSecurityMessageDisplay.setMessage(message);
     }
 
-    protected abstract int getPromtReasonStringRes(int reason);
+    protected abstract int getPromptReasonStringRes(int reason);
 
     // Cause a VIRTUAL_KEY vibration
     public void doHapticKeyClick() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index 27a3f7d..f1a5ca9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -34,6 +34,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.settingslib.Utils;
 
 import java.io.File;
 
@@ -171,10 +172,14 @@
         mSecurityContainer.showPromptReason(reason);
     }
 
-    public void showMessage(String message, int color) {
+    public void showMessage(CharSequence message, int color) {
         mSecurityContainer.showMessage(message, color);
     }
 
+    public void showErrorMessage(CharSequence message) {
+        showMessage(message, Utils.getColorError(mContext));
+    }
+
     /**
      * Dismisses the keyguard by going to the next screen or making it gone.
      * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index b6184a8..ff5f5e7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -117,7 +117,7 @@
     }
 
     @Override
-    protected int getPromtReasonStringRes(int reason) {
+    protected int getPromptReasonStringRes(int reason) {
         switch (reason) {
             case PROMPT_REASON_RESTART:
                 return R.string.kg_prompt_reason_restart_password;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index d636316..cb066a1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -398,7 +398,7 @@
     }
 
     @Override
-    public void showMessage(String message, int color) {
+    public void showMessage(CharSequence message, int color) {
         mSecurityMessageDisplay.setNextMessageColor(color);
         mSecurityMessageDisplay.setMessage(message);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index c04ae68..6539ccf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -103,7 +103,7 @@
     }
 
     @Override
-    protected int getPromtReasonStringRes(int reason) {
+    protected int getPromptReasonStringRes(int reason) {
         switch (reason) {
             case PROMPT_REASON_RESTART:
                 return R.string.kg_prompt_reason_restart_pin;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 9f39321..8dc4609 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -543,8 +543,7 @@
         }
     }
 
-
-    public void showMessage(String message, int color) {
+    public void showMessage(CharSequence message, int color) {
         if (mCurrentSecuritySelection != SecurityMode.None) {
             getSecurityView(mCurrentSecuritySelection).showMessage(message, color);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 8290842..360dba3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -106,7 +106,7 @@
      * @param message the message to show
      * @param color the color to use
      */
-    void showMessage(String message, int color);
+    void showMessage(CharSequence message, int color);
 
     /**
      * Instruct the view to show usability hints, if any.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index 6012c45..a2ff8f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -139,7 +139,7 @@
     }
 
     @Override
-    public void showMessage(String message, int color) {
+    public void showMessage(CharSequence message, int color) {
         KeyguardSecurityView ksv = getSecurityView();
         if (ksv != null) {
             ksv.showMessage(message, color);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 6e0b56e..e7432ba 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -168,7 +168,7 @@
     }
 
     @Override
-    protected int getPromtReasonStringRes(int reason) {
+    protected int getPromptReasonStringRes(int reason) {
         // No message on SIM Pin
         return 0;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 876d170..afee8ec 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -211,7 +211,7 @@
     }
 
     @Override
-    protected int getPromtReasonStringRes(int reason) {
+    protected int getPromptReasonStringRes(int reason) {
         // No message on SIM Puk
         return 0;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
index eff84c6..5c68123 100644
--- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
@@ -99,4 +99,10 @@
      * Invoked when the secondary display showing a keyguard window changes.
      */
     void onSecondaryDisplayShowingChanged(int displayId);
+
+    /**
+     * Consumes a message that was enqueued to be displayed on the next time the bouncer shows up.
+     * @return Message that should be displayed above the challenge.
+     */
+    CharSequence consumeCustomMessage();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index e7e70af..7403ddc 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -40,6 +40,8 @@
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.PluginManagerImpl;
 import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.power.EnhancedEstimatesImpl;
 import com.android.systemui.power.PowerNotificationWarnings;
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -310,6 +312,8 @@
 
         mProviders.put(OverviewProxyService.class, () -> new OverviewProxyService(mContext));
 
+        mProviders.put(EnhancedEstimates.class, () -> new EnhancedEstimatesImpl());
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
index a102260..f41425a 100644
--- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
+++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
@@ -38,6 +38,9 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+
 import java.util.Collections;
 import java.util.List;
 
@@ -45,18 +48,28 @@
  * Emulates a display cutout by drawing its shape in an overlay as supplied by
  * {@link DisplayCutout}.
  */
-public class EmulatedDisplayCutout extends SystemUI {
+public class EmulatedDisplayCutout extends SystemUI implements ConfigurationListener {
     private View mOverlay;
     private boolean mAttached;
     private WindowManager mWindowManager;
 
     @Override
     public void start() {
+        Dependency.get(ConfigurationController.class).addCallback(this);
+
         mWindowManager = mContext.getSystemService(WindowManager.class);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.EMULATE_DISPLAY_CUTOUT),
-                false, mObserver, UserHandle.USER_ALL);
-        mObserver.onChange(false);
+        updateAttached();
+    }
+
+    @Override
+    public void onOverlayChanged() {
+        updateAttached();
+    }
+
+    private void updateAttached() {
+        boolean shouldAttach = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
+        setAttached(shouldAttach);
     }
 
     private void setAttached(boolean attached) {
@@ -94,17 +107,6 @@
         return lp;
     }
 
-    private ContentObserver mObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
-        @Override
-        public void onChange(boolean selfChange) {
-            boolean emulateCutout = Settings.Global.getInt(
-                    mContext.getContentResolver(), Settings.Global.EMULATE_DISPLAY_CUTOUT,
-                    Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF)
-                    != Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF;
-            setAttached(emulateCutout);
-        }
-    };
-
     private static class CutoutView extends View {
         private final Paint mPaint = new Paint();
         private final Path mBounds = new Path();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index e008148..0f34513 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -24,10 +24,12 @@
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ServiceConnection;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.graphics.Point;
@@ -36,7 +38,9 @@
 import android.net.ConnectivityManager;
 import android.os.Build;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Message;
+import android.os.Messenger;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -77,6 +81,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.util.EmergencyAffordanceManager;
+import com.android.internal.util.ScreenshotHelper;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.HardwareUiLayout;
@@ -117,6 +122,7 @@
     private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
     private static final String GLOBAL_ACTION_KEY_RESTART = "restart";
     private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout";
+    private static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
 
     private final Context mContext;
     private final GlobalActionsManager mWindowManagerFuncs;
@@ -143,6 +149,7 @@
     private boolean mHasLogoutButton;
     private final boolean mShowSilentToggle;
     private final EmergencyAffordanceManager mEmergencyAffordanceManager;
+    private final ScreenshotHelper mScreenshotHelper;
 
     /**
      * @param context everything needs a context :(
@@ -183,6 +190,7 @@
                 R.bool.config_useFixedVolume);
 
         mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
+        mScreenshotHelper = new ScreenshotHelper(context);
     }
 
     /**
@@ -340,6 +348,8 @@
                 mItems.add(getAssistAction());
             } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
                 mItems.add(new RestartAction());
+            } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) {
+                mItems.add(new ScreenshotAction());
             } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) {
                 if (mDevicePolicyManager.isLogoutEnabled()
                         && getCurrentUser().id != UserHandle.USER_SYSTEM) {
@@ -458,6 +468,38 @@
     }
 
 
+    private class ScreenshotAction extends SinglePressAction {
+        public ScreenshotAction() {
+            super(R.drawable.ic_screenshot, R.string.global_action_screenshot);
+        }
+
+        @Override
+        public void onPress() {
+            // Add a little delay before executing, to give the
+            // dialog a chance to go away before it takes a
+            // screenshot.
+            // TODO: instead, omit global action dialog layer
+            mHandler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    mScreenshotHelper.takeScreenshot(1, true, true, mHandler);
+                    MetricsLogger.action(mContext,
+                            MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
+                }
+            }, 500);
+        }
+
+        @Override
+        public boolean showDuringKeyguard() {
+            return true;
+        }
+
+        @Override
+        public boolean showBeforeProvisioning() {
+            return false;
+        }
+    }
+
     private class BugReportAction extends SinglePressAction implements LongPressAction {
 
         public BugReportAction() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 2a5ae0d..22b41a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -96,9 +96,9 @@
         }
 
         @Override // Binder interface
-        public void dismiss(IKeyguardDismissCallback callback) {
+        public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
             checkPermission();
-            mKeyguardViewMediator.dismiss(callback);
+            mKeyguardViewMediator.dismiss(callback, message);
         }
 
         @Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index bd46c5f..e49e80d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -103,11 +103,12 @@
 
     @Override
     public Slice onBindSlice(Uri sliceUri) {
-        ListBuilder builder = new ListBuilder(mSliceUri)
-                .addRow(new RowBuilder(mDateUri).setTitle(mLastText));
+        ListBuilder builder = new ListBuilder(getContext(), mSliceUri);
+        builder.addRow(new RowBuilder(builder, mDateUri).setTitle(mLastText));
         if (!TextUtils.isEmpty(mNextAlarm)) {
             Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big);
-            builder.addRow(new RowBuilder(mAlarmUri).setTitle(mNextAlarm).addEndItem(icon));
+            builder.addRow(new RowBuilder(builder, mAlarmUri)
+                    .setTitle(mNextAlarm).addEndItem(icon));
         }
 
         return builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 91ae448..653e500 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -25,7 +25,6 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 
-
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.NotificationManager;
@@ -344,6 +343,7 @@
     private boolean mWakeAndUnlocking;
     private IKeyguardDrawnCallback mDrawnCallback;
     private boolean mLockWhenSimRemoved;
+    private CharSequence mCustomMessage;
 
     KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
 
@@ -368,7 +368,7 @@
                     return;
                 } else if (info.isGuest() || info.isDemo()) {
                     // If we just switched to a guest, try to dismiss keyguard.
-                    dismiss(null /* callback */);
+                    dismiss(null /* callback */, null /* message */);
                 }
             }
         }
@@ -654,6 +654,13 @@
         }
 
         @Override
+        public CharSequence consumeCustomMessage() {
+            final CharSequence message = mCustomMessage;
+            mCustomMessage = null;
+            return message;
+        }
+
+        @Override
         public void onSecondaryDisplayShowingChanged(int displayId) {
             synchronized (KeyguardViewMediator.this) {
                 setShowingLocked(mShowing, displayId, false);
@@ -1321,20 +1328,22 @@
     /**
      * Dismiss the keyguard through the security layers.
      * @param callback Callback to be informed about the result
+     * @param message Message that should be displayed on the bouncer.
      */
-    private void handleDismiss(IKeyguardDismissCallback callback) {
+    private void handleDismiss(IKeyguardDismissCallback callback, CharSequence message) {
         if (mShowing) {
             if (callback != null) {
                 mDismissCallbackRegistry.addCallback(callback);
             }
+            mCustomMessage = message;
             mStatusBarKeyguardViewManager.dismissAndCollapse();
         } else if (callback != null) {
             new DismissCallbackWrapper(callback).notifyDismissError();
         }
     }
 
-    public void dismiss(IKeyguardDismissCallback callback) {
-        mHandler.obtainMessage(DISMISS, callback).sendToTarget();
+    public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
+        mHandler.obtainMessage(DISMISS, new DismissMessage(callback, message)).sendToTarget();
     }
 
     /**
@@ -1551,7 +1560,8 @@
                     }
                     break;
                 case DISMISS:
-                    handleDismiss((IKeyguardDismissCallback) msg.obj);
+                    final DismissMessage message = (DismissMessage) msg.obj;
+                    handleDismiss(message.getCallback(), message.getMessage());
                     break;
                 case START_KEYGUARD_EXIT_ANIM:
                     Trace.beginSection("KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
@@ -2161,4 +2171,22 @@
             }
         }
     }
+
+    private static class DismissMessage {
+        private final CharSequence mMessage;
+        private final IKeyguardDismissCallback mCallback;
+
+        DismissMessage(IKeyguardDismissCallback callback, CharSequence message) {
+            mCallback = callback;
+            mMessage = message;
+        }
+
+        public IKeyguardDismissCallback getCallback() {
+            return mCallback;
+        }
+
+        public CharSequence getMessage() {
+            return mMessage;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
new file mode 100644
index 0000000..8f41a60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
@@ -0,0 +1,8 @@
+package com.android.systemui.power;
+
+public interface EnhancedEstimates {
+
+    boolean isHybridNotificationEnabled();
+
+    Estimate getEstimate();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
new file mode 100644
index 0000000..d447542
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
@@ -0,0 +1,16 @@
+package com.android.systemui.power;
+
+import android.util.Log;
+
+public class EnhancedEstimatesImpl implements EnhancedEstimates {
+
+    @Override
+    public boolean isHybridNotificationEnabled() {
+        return false;
+    }
+
+    @Override
+    public Estimate getEstimate() {
+        return null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.java b/packages/SystemUI/src/com/android/systemui/power/Estimate.java
new file mode 100644
index 0000000..12a8f0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/Estimate.java
@@ -0,0 +1,11 @@
+package com.android.systemui.power;
+
+public class Estimate {
+    public final long estimateMillis;
+    public final boolean isBasedOnUsage;
+
+    public Estimate(long estimateMillis, boolean isBasedOnUsage) {
+        this.estimateMillis = estimateMillis;
+        this.isBasedOnUsage = isBasedOnUsage;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index c29b362..736286f 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -17,40 +17,40 @@
 package com.android.systemui.power;
 
 import android.app.Notification;
-import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
 import android.content.DialogInterface.OnDismissListener;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.icu.text.MeasureFormat;
+import android.icu.text.MeasureFormat.FormatWidth;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
 import android.media.AudioAttributes;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
-import android.os.SystemClock;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.support.annotation.VisibleForTesting;
+import android.text.format.DateUtils;
 import android.util.Slog;
 
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
-import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.NotificationChannels;
 
 import java.io.PrintWriter;
 import java.text.NumberFormat;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
 
 public class PowerNotificationWarnings implements PowerUI.WarningsUI {
     private static final String TAG = PowerUI.TAG + ".Notification";
@@ -96,8 +96,9 @@
     private long mScreenOffTime;
     private int mShowing;
 
-    private long mBucketDroppedNegativeTimeMs;
+    private long mWarningTriggerTimeMs;
 
+    private Estimate mEstimate;
     private boolean mWarning;
     private boolean mPlaySound;
     private boolean mInvalidCharger;
@@ -130,14 +131,22 @@
     public void update(int batteryLevel, int bucket, long screenOffTime) {
         mBatteryLevel = batteryLevel;
         if (bucket >= 0) {
-            mBucketDroppedNegativeTimeMs = 0;
+            mWarningTriggerTimeMs = 0;
         } else if (bucket < mBucket) {
-            mBucketDroppedNegativeTimeMs = System.currentTimeMillis();
+            mWarningTriggerTimeMs = System.currentTimeMillis();
         }
         mBucket = bucket;
         mScreenOffTime = screenOffTime;
     }
 
+    @Override
+    public void updateEstimate(Estimate estimate) {
+        mEstimate = estimate;
+        if (estimate.estimateMillis <= PowerUI.THREE_HOURS_IN_MILLIS) {
+            mWarningTriggerTimeMs = System.currentTimeMillis();
+        }
+    }
+
     private void updateNotification() {
         if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound="
                 + mPlaySound + " mInvalidCharger=" + mInvalidCharger);
@@ -171,25 +180,43 @@
         mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, n, UserHandle.ALL);
     }
 
-    private void showWarningNotification() {
-        final int textRes = R.string.battery_low_percent_format;
+    protected void showWarningNotification() {
         final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0);
 
+        // get standard notification copy
+        String title = mContext.getString(R.string.battery_low_title);
+        String contentText = mContext.getString(R.string.battery_low_percent_format, percentage);
+
+        // override notification copy if hybrid notification enabled
+        if (mEstimate != null) {
+            title = mContext.getString(R.string.battery_low_title_hybrid);
+            contentText = mContext.getString(
+                    mEstimate.isBasedOnUsage
+                            ? R.string.battery_low_percent_format_hybrid
+                            : R.string.battery_low_percent_format_hybrid_short,
+                    percentage,
+                    getTimeRemainingFormatted());
+        }
+
         final Notification.Builder nb =
                 new Notification.Builder(mContext, NotificationChannels.BATTERY)
                         .setSmallIcon(R.drawable.ic_power_low)
                         // Bump the notification when the bucket dropped.
-                        .setWhen(mBucketDroppedNegativeTimeMs)
+                        .setWhen(mWarningTriggerTimeMs)
                         .setShowWhen(false)
-                        .setContentTitle(mContext.getString(R.string.battery_low_title))
-                        .setContentText(mContext.getString(textRes, percentage))
+                        .setContentTitle(title)
+                        .setContentText(contentText)
                         .setOnlyAlertOnce(true)
                         .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING))
-                        .setVisibility(Notification.VISIBILITY_PUBLIC)
-                        .setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
+                        .setVisibility(Notification.VISIBILITY_PUBLIC);
         if (hasBatterySettings()) {
             nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS));
         }
+        // Make the notification red if the percentage goes below a certain amount or the time
+        // remaining estimate is disabled
+        if (mEstimate == null || mBucket < 0) {
+            nb.setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
+        }
         nb.addAction(0,
                 mContext.getString(R.string.battery_saver_start_action),
                 pendingBroadcast(ACTION_START_SAVER));
@@ -201,6 +228,23 @@
         mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL);
     }
 
+    @VisibleForTesting
+    String getTimeRemainingFormatted() {
+        final Locale currentLocale = mContext.getResources().getConfiguration().getLocales().get(0);
+        MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.NARROW);
+
+        final long remainder = mEstimate.estimateMillis % DateUtils.HOUR_IN_MILLIS;
+        final long hours = TimeUnit.MILLISECONDS.toHours(
+                mEstimate.estimateMillis - remainder);
+        // round down to the nearest 15 min for now to not appear overly precise
+        final long minutes = TimeUnit.MILLISECONDS.toMinutes(
+                remainder - (remainder % TimeUnit.MINUTES.toMillis(15)));
+        final Measure hoursMeasure = new Measure(hours, MeasureUnit.HOUR);
+        final Measure minutesMeasure = new Measure(minutes, MeasureUnit.MINUTE);
+
+        return frmt.formatMeasures(hoursMeasure, minutesMeasure);
+    }
+
     private PendingIntent pendingBroadcast(String action) {
         return PendingIntent.getBroadcastAsUser(mContext,
                 0, new Intent(action), 0, UserHandle.CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index c1a3623..c5aab60 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -52,6 +52,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
 
 public class PowerUI extends SystemUI {
     static final String TAG = "PowerUI";
@@ -59,6 +60,7 @@
     private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS;
     private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS;
     private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer
+    static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3;
 
     private final Handler mHandler = new Handler();
     private final Receiver mReceiver = new Receiver();
@@ -68,9 +70,11 @@
     private WarningsUI mWarnings;
     private final Configuration mLastConfiguration = new Configuration();
     private int mBatteryLevel = 100;
+    private long mTimeRemaining = Long.MAX_VALUE;
     private int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
     private int mPlugType = 0;
     private int mInvalidCharger = 0;
+    private EnhancedEstimates mEnhancedEstimates;
 
     private int mLowBatteryAlertCloseLevel;
     private final int[] mLowBatteryReminderLevels = new int[2];
@@ -83,8 +87,8 @@
     private long mNextLogTime;
     private IThermalService mThermalService;
 
-    // We create a method reference here so that we are guaranteed that we can remove a callback
     // by using the same instance (method references are not guaranteed to be the same object
+    // We create a method reference here so that we are guaranteed that we can remove a callback
     // each time they are created).
     private final Runnable mUpdateTempCallback = this::updateTemperatureWarning;
 
@@ -94,6 +98,7 @@
                 mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE);
         mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
         mWarnings = Dependency.get(WarningsUI.class);
+        mEnhancedEstimates = Dependency.get(EnhancedEstimates.class);
         mLastConfiguration.setTo(mContext.getResources().getConfiguration());
 
         ContentObserver obs = new ContentObserver(mHandler) {
@@ -131,10 +136,15 @@
                 com.android.internal.R.integer.config_criticalBatteryWarningLevel);
 
         final ContentResolver resolver = mContext.getContentResolver();
-        int defWarnLevel = mContext.getResources().getInteger(
+        final int defWarnLevel = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_lowBatteryWarningLevel);
-        int warnLevel = Settings.Global.getInt(resolver,
+        final int lowPowerModeTriggerLevel = Settings.Global.getInt(resolver,
                 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, defWarnLevel);
+
+        // NOTE: Keep the logic in sync with BatteryService.
+        // TODO: Propagate this value from BatteryService to system UI, really.
+        int warnLevel =  Math.min(defWarnLevel, lowPowerModeTriggerLevel);
+
         if (warnLevel == 0) {
             warnLevel = defWarnLevel;
         }
@@ -231,21 +241,9 @@
                     return;
                 }
 
-                boolean isPowerSaver = mPowerManager.isPowerSaveMode();
-                if (!plugged
-                        && !isPowerSaver
-                        && (bucket < oldBucket || oldPlugged)
-                        && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
-                        && bucket < 0) {
+                // Show the correct version of low battery warning if needed
+                maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket);
 
-                    // only play SFX when the dialog comes up or the bucket changes
-                    final boolean playSound = bucket != oldBucket || oldPlugged;
-                    mWarnings.showLowBatteryWarning(playSound);
-                } else if (isPowerSaver || plugged || (bucket > oldBucket && bucket > 0)) {
-                    mWarnings.dismissLowBatteryWarning();
-                } else {
-                    mWarnings.updateLowBatteryWarning();
-                }
             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                 mScreenOffTime = SystemClock.elapsedRealtime();
             } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
@@ -256,7 +254,65 @@
                 Slog.w(TAG, "unknown intent: " + intent);
             }
         }
-    };
+    }
+
+    protected void maybeShowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
+        int bucket) {
+        boolean isPowerSaver = mPowerManager.isPowerSaveMode();
+        // only play SFX when the dialog comes up or the bucket changes
+        final boolean playSound = bucket != oldBucket || oldPlugged;
+        long oldTimeRemaining = mTimeRemaining;
+        if (mEnhancedEstimates.isHybridNotificationEnabled()) {
+            final Estimate estimate = mEnhancedEstimates.getEstimate();
+            // Turbo is not always booted once SysUI is running so we have ot make sure we actually
+            // get data back
+            if (estimate != null) {
+                mTimeRemaining = estimate.estimateMillis;
+                mWarnings.updateEstimate(estimate);
+            }
+        }
+
+        if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket, oldTimeRemaining,
+                mTimeRemaining,
+                isPowerSaver, mBatteryStatus)) {
+            mWarnings.showLowBatteryWarning(playSound);
+        } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining,
+                isPowerSaver)) {
+            mWarnings.dismissLowBatteryWarning();
+        } else {
+            mWarnings.updateLowBatteryWarning();
+        }
+    }
+
+    @VisibleForTesting
+    boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
+            int bucket, long oldTimeRemaining, long timeRemaining,
+            boolean isPowerSaver, int mBatteryStatus) {
+        return !plugged
+                && !isPowerSaver
+                && (((bucket < oldBucket || oldPlugged) && bucket < 0)
+                        || (mEnhancedEstimates.isHybridNotificationEnabled()
+                                && timeRemaining < THREE_HOURS_IN_MILLIS
+                                && isHourLess(oldTimeRemaining, timeRemaining)))
+                && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
+    }
+
+    private boolean isHourLess(long oldTimeRemaining, long timeRemaining) {
+        final long dif = oldTimeRemaining - timeRemaining;
+        return dif >= TimeUnit.HOURS.toMillis(1);
+    }
+
+    @VisibleForTesting
+    boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket,
+            long timeRemaining, boolean isPowerSaver) {
+        final boolean hybridWouldDismiss = mEnhancedEstimates.isHybridNotificationEnabled()
+                && timeRemaining > THREE_HOURS_IN_MILLIS;
+        final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0);
+        return isPowerSaver
+                || plugged
+                || (standardWouldDismiss && (!mEnhancedEstimates.isHybridNotificationEnabled()
+                        || hybridWouldDismiss));
+    }
 
     private void initTemperatureWarning() {
         ContentResolver resolver = mContext.getContentResolver();
@@ -428,6 +484,7 @@
 
     public interface WarningsUI {
         void update(int batteryLevel, int bucket, long screenOffTime);
+        void updateEstimate(Estimate estimate);
         void dismissLowBatteryWarning();
         void showLowBatteryWarning(boolean playSound);
         void dismissInvalidChargerWarning();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index f3bae20..34b8bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -67,6 +67,8 @@
                 case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
                     mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
                     break;
+                default:
+                    Log.d(TAG, "Invalid screenshot option: " + msg.what);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
index a83e659..78ee040 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -89,6 +89,10 @@
         return mAlpha != null ? mAlpha : 1;
     }
 
+    public KeyButtonDrawable getImageDrawable() {
+        return mImageDrawable;
+    }
+
     public void setImageDrawable(KeyButtonDrawable drawable) {
         mImageDrawable = drawable;
         final int N = mViews.size();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index b71ebfd..699e8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -131,6 +131,10 @@
             mRoot.setVisibility(View.VISIBLE);
             mKeyguardView.onResume();
             showPromptReason(mBouncerPromptReason);
+            final CharSequence customMessage = mCallback.consumeCustomMessage();
+            if (customMessage != null) {
+                mKeyguardView.showErrorMessage(customMessage);
+            }
             // We might still be collapsed and the view didn't have time to layout yet or still
             // be small, let's wait on the predraw to do the animation in that case.
             if (mKeyguardView.getHeight() != 0 && mKeyguardView.getHeight() != mStatusBarHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 7f4deb0..0f8d59b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -61,7 +61,7 @@
     public void setWorkModeEnabled(boolean enableWorkMode) {
         synchronized (mProfiles) {
             for (UserInfo ui : mProfiles) {
-                if (!mUserManager.trySetQuietModeEnabled(!enableWorkMode, UserHandle.of(ui.id))) {
+                if (!mUserManager.requestQuietModeEnabled(!enableWorkMode, UserHandle.of(ui.id))) {
                     StatusBarManager statusBarManager = (StatusBarManager) mContext
                             .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
                     statusBarManager.collapsePanels();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 695168e..70ec45e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -24,6 +24,10 @@
 import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
@@ -39,6 +43,7 @@
 import android.database.ContentObserver;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
 import android.inputmethodservice.InputMethodService;
 import android.os.Binder;
 import android.os.Bundle;
@@ -70,17 +75,22 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.systemui.Dependency;
 import com.android.systemui.OverviewProxyService;
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 import com.android.systemui.statusbar.policy.KeyButtonView;
+import com.android.systemui.statusbar.policy.RotationLockController;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 import java.io.FileDescriptor;
@@ -101,6 +111,8 @@
     /** Allow some time inbetween the long press for back and recents. */
     private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
 
+    private static final int ROTATE_SUGGESTION_TIMEOUT_MS = 4000;
+
     protected NavigationBarView mNavigationBarView = null;
     protected AssistManager mAssistManager;
 
@@ -130,6 +142,15 @@
 
     public boolean mHomeBlockedThisTouch;
 
+    private int mLastRotationSuggestion;
+    private RotationLockController mRotationLockController;
+    private TaskStackListenerImpl mTaskStackListener;
+
+    private final Runnable mRemoveRotationProposal = () -> setRotateSuggestionButtonState(false);
+    private Animator mRotateShowAnimator;
+    private Animator mRotateHideAnimator;
+
+
     // ----- Fragment Lifecycle Callbacks -----
 
     @Override
@@ -163,6 +184,12 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
+
+        mRotationLockController = Dependency.get(RotationLockController.class);
+
+        // Register the task stack listener
+        mTaskStackListener = new TaskStackListenerImpl();
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
     }
 
     @Override
@@ -178,6 +205,9 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
+
+        // Unregister the task stack listener
+        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
     }
 
     @Override
@@ -304,6 +334,92 @@
         }
     }
 
+    @Override
+    public void onRotationProposal(final int rotation) {
+        // This method will only be called if rotation is valid but will include proposals for the
+        // current system rotation
+        Handler h = getView().getHandler();
+        if (rotation == mWindowManager.getDefaultDisplay().getRotation()) {
+            // Use this as a signal to remove any current suggestions
+            h.removeCallbacks(mRemoveRotationProposal);
+            setRotateSuggestionButtonState(false);
+        } else {
+            mLastRotationSuggestion = rotation; // Remember rotation for click
+            setRotateSuggestionButtonState(true);
+            h.removeCallbacks(mRemoveRotationProposal); // Stop any pending removal
+            h.postDelayed(mRemoveRotationProposal,
+                    ROTATE_SUGGESTION_TIMEOUT_MS); // Schedule timeout
+        }
+    }
+
+    public void setRotateSuggestionButtonState(final boolean visible) {
+        setRotateSuggestionButtonState(visible, false);
+    }
+
+    public void setRotateSuggestionButtonState(final boolean visible, final boolean skipAnim) {
+        ButtonDispatcher rotBtn = mNavigationBarView.getRotateSuggestionButton();
+        boolean currentlyVisible = rotBtn.getVisibility() == View.VISIBLE;
+
+        // Rerun a show animation to indicate change but don't rerun a hide animation
+        if (!visible && !currentlyVisible) return;
+
+        View currentView = mNavigationBarView.getRotateSuggestionButton().getCurrentView();
+        if (currentView == null) return;
+
+        KeyButtonDrawable kbd = mNavigationBarView.getRotateSuggestionButton().getImageDrawable();
+        if (kbd == null) return;
+
+        AnimatedVectorDrawable animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0);
+        if (visible) { // Appear and change
+            rotBtn.setVisibility(View.VISIBLE);
+
+            if (skipAnim) {
+                currentView.setAlpha(1f);
+                return;
+            }
+
+            // Start a new animation if running
+            if (mRotateShowAnimator != null) mRotateShowAnimator.pause();
+            if (mRotateHideAnimator != null) mRotateHideAnimator.pause();
+
+            ObjectAnimator appearFade = ObjectAnimator.ofFloat(currentView, "alpha",
+                    0f, 1f);
+            appearFade.setDuration(100);
+            appearFade.setInterpolator(Interpolators.LINEAR);
+            mRotateShowAnimator = appearFade;
+            appearFade.start();
+
+            // Run the rotate icon's animation
+            animIcon.reset();
+            animIcon.start();
+        } else { // Hide
+
+            if (skipAnim) {
+                rotBtn.setVisibility(View.INVISIBLE);
+                return;
+            }
+
+            // Don't start any new hide animations if one is running
+            if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
+            // Pause any active show animations but don't reset the AVD to avoid jumps
+            if (mRotateShowAnimator != null) mRotateShowAnimator.pause();
+
+            ObjectAnimator fadeOut = ObjectAnimator.ofFloat(currentView, "alpha",
+                    0f);
+            fadeOut.setDuration(100);
+            fadeOut.setInterpolator(Interpolators.LINEAR);
+            fadeOut.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    rotBtn.setVisibility(View.INVISIBLE);
+                }
+            });
+
+            mRotateHideAnimator = fadeOut;
+            fadeOut.start();
+        }
+    }
+
     // Injected from StatusBar at creation.
     public void setCurrentSysuiVisibility(int systemUiVisibility) {
         mSystemUiVisibility = systemUiVisibility;
@@ -406,6 +522,9 @@
         accessibilityButton.setOnClickListener(this::onAccessibilityClick);
         accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
         updateAccessibilityServicesState(mAccessibilityManager);
+
+        ButtonDispatcher rotateSuggestionButton = mNavigationBarView.getRotateSuggestionButton();
+        rotateSuggestionButton.setOnClickListener(this::onRotateSuggestionClick);
     }
 
     private boolean onHomeTouch(View v, MotionEvent event) {
@@ -598,6 +717,10 @@
         mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection);
     }
 
+    private void onRotateSuggestionClick(View v) {
+        mRotationLockController.setRotationLockedAtAngle(true, mLastRotationSuggestion);
+    }
+
     // ----- Methods that StatusBar talks to (should be minimized) -----
 
     public void setLightBarController(LightBarController lightBarController) {
@@ -646,6 +769,13 @@
     private final Stub mRotationWatcher = new Stub() {
         @Override
         public void onRotationChanged(int rotation) throws RemoteException {
+            // If the screen rotation changes while locked, update lock rotation to flow with
+            // new screen rotation and hide any showing suggestions.
+            if (mRotationLockController.isRotationLocked()) {
+                mRotationLockController.setRotationLockedAtAngle(true, rotation);
+                setRotateSuggestionButtonState(false, true);
+            }
+
             // We need this to be scheduled as early as possible to beat the redrawing of
             // window in response to the orientation change.
             Handler h = getView().getHandler();
@@ -671,6 +801,31 @@
         }
     };
 
+    class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
+        // Invalidate any rotation suggestion on task change or activity orientation change
+        // Note: all callbacks happen on main thread
+
+        @Override
+        public void onTaskStackChanged() {
+            setRotateSuggestionButtonState(false);
+        }
+
+        @Override
+        public void onTaskRemoved(int taskId) {
+            setRotateSuggestionButtonState(false);
+        }
+
+        @Override
+        public void onTaskMovedToFront(int taskId) {
+            setRotateSuggestionButtonState(false);
+        }
+
+        @Override
+        public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
+            setRotateSuggestionButtonState(false);
+        }
+    }
+
     public static View create(Context context, FragmentListener listener) {
         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 4e79314b..b8b309b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -63,6 +63,7 @@
     public static final String RECENT = "recent";
     public static final String NAVSPACE = "space";
     public static final String CLIPBOARD = "clipboard";
+    public static final String ROTATE = "rotate";
     public static final String KEY = "key";
     public static final String LEFT = "left";
     public static final String RIGHT = "right";
@@ -311,7 +312,7 @@
         View v = null;
         String button = extractButton(buttonSpec);
         if (LEFT.equals(button)) {
-            String s = Dependency.get(TunerService.class).getValue(NAV_BAR_LEFT, NAVSPACE);
+            String s = Dependency.get(TunerService.class).getValue(NAV_BAR_LEFT, ROTATE);
             button = extractButton(s);
         } else if (RIGHT.equals(button)) {
             String s = Dependency.get(TunerService.class).getValue(NAV_BAR_RIGHT, MENU_IME);
@@ -334,6 +335,8 @@
             v = inflater.inflate(R.layout.nav_key_space, parent, false);
         } else if (CLIPBOARD.equals(button)) {
             v = inflater.inflate(R.layout.clipboard, parent, false);
+        } else if (ROTATE.equals(button)) {
+            v = inflater.inflate(R.layout.rotate_suggestion, parent, false);
         } else if (button.startsWith(KEY)) {
             String uri = extractImage(button);
             int code = extractKeycode(button);
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 392581d..006a85b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -31,6 +31,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.RemoteException;
+import android.support.annotation.ColorInt;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
@@ -56,6 +57,7 @@
 import com.android.systemui.plugins.statusbar.phone.NavGesture;
 import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
 import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.policy.TintedKeyButtonDrawable;
 import com.android.systemui.statusbar.policy.DeadZone;
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 
@@ -94,6 +96,7 @@
     private KeyButtonDrawable mImeIcon;
     private KeyButtonDrawable mMenuIcon;
     private KeyButtonDrawable mAccessibilityIcon;
+    private KeyButtonDrawable mRotateSuggestionIcon;
 
     private GestureHelper mGestureHelper;
     private DeadZone mDeadZone;
@@ -229,6 +232,9 @@
         mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
         mButtonDispatchers.put(R.id.accessibility_button,
                 new ButtonDispatcher(R.id.accessibility_button));
+        mButtonDispatchers.put(R.id.rotate_suggestion,
+                new ButtonDispatcher(R.id.rotate_suggestion));
+
         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
     }
 
@@ -305,6 +311,10 @@
         return mButtonDispatchers.get(R.id.accessibility_button);
     }
 
+    public ButtonDispatcher getRotateSuggestionButton() {
+        return mButtonDispatchers.get(R.id.rotate_suggestion);
+    }
+
     public SparseArray<ButtonDispatcher> getButtonDispatchers() {
         return mButtonDispatchers;
     }
@@ -349,6 +359,11 @@
             mImeIcon = getDrawable(darkContext, lightContext,
                     R.drawable.ic_ime_switcher_default, R.drawable.ic_ime_switcher_default);
 
+            int lightColor = Utils.getColorAttr(lightContext, R.attr.singleToneColor);
+            int darkColor = Utils.getColorAttr(darkContext, R.attr.singleToneColor);
+            mRotateSuggestionIcon = getDrawable(ctx, R.drawable.ic_sysbar_rotate_button,
+                    lightColor, darkColor);
+
             if (ALTERNATE_CAR_MODE_UI) {
                 updateCarModeIcons(ctx);
             }
@@ -366,6 +381,11 @@
                 darkContext.getDrawable(darkIcon));
     }
 
+    private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int icon,
+            @ColorInt int lightColor, @ColorInt int darkColor) {
+        return TintedKeyButtonDrawable.create(ctx.getDrawable(icon), lightColor, darkColor);
+    }
+
     @Override
     public void setLayoutDirection(int layoutDirection) {
         // Reload all the icons
@@ -439,6 +459,8 @@
         setAccessibilityButtonState(mShowAccessibilityButton, mLongClickableAccessibilityButton);
         getAccessibilityButton().setImageDrawable(mAccessibilityIcon);
 
+        getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon);
+
         setDisabledFlags(mDisabledFlags, true);
 
         mBarTransitions.reapplyDarkIntensity();
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 8504d8e..c667309 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -175,13 +175,18 @@
 
     public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
             boolean afterKeyguardGone) {
+        dismissWithAction(r, cancelAction, afterKeyguardGone, null /* message */);
+    }
+
+    public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
+            boolean afterKeyguardGone, String message) {
         if (mShowing) {
             cancelPendingWakeupAction();
             // If we're dozing, this needs to be delayed until after we wake up - unless we're
             // wake-and-unlocking, because there dozing will last until the end of the transition.
             if (mDozing && !isWakeAndUnlocking()) {
                 mPendingWakeupAction = new DismissWithActionRequest(
-                        r, cancelAction, afterKeyguardGone);
+                        r, cancelAction, afterKeyguardGone, message);
                 return;
             }
 
@@ -632,7 +637,7 @@
         if (request != null) {
             if (mShowing) {
                 dismissWithAction(request.dismissAction, request.cancelAction,
-                        request.afterKeyguardGone);
+                        request.afterKeyguardGone, request.message);
             } else if (request.dismissAction != null) {
                 request.dismissAction.onDismiss();
             }
@@ -651,12 +656,14 @@
         final OnDismissAction dismissAction;
         final Runnable cancelAction;
         final boolean afterKeyguardGone;
+        final String message;
 
         DismissWithActionRequest(OnDismissAction dismissAction, Runnable cancelAction,
-                boolean afterKeyguardGone) {
+                boolean afterKeyguardGone, String message) {
             this.dismissAction = dismissAction;
             this.cancelAction = cancelAction;
             this.afterKeyguardGone = afterKeyguardGone;
+            this.message = message;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
index 21a96e2..cce9d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
@@ -39,7 +39,7 @@
         }
     }
 
-    private KeyButtonDrawable(Drawable[] drawables) {
+    protected KeyButtonDrawable(Drawable[] drawables) {
         super(drawables);
         for (int i = 0; i < drawables.length; i++) {
             setLayerGravity(i, Gravity.CENTER);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java
new file mode 100644
index 0000000..acf9c00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java
@@ -0,0 +1,57 @@
+/*
+ * 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
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.annotation.ColorInt;
+import android.graphics.drawable.Drawable;
+
+import com.android.internal.graphics.ColorUtils;
+
+/**
+ * Drawable for {@link KeyButtonView}s which contains a single asset and colors for light and dark
+ * navigation bar mode.
+ */
+public class TintedKeyButtonDrawable extends KeyButtonDrawable {
+
+    private final int mLightColor;
+    private final int mDarkColor;
+
+    public static TintedKeyButtonDrawable create(Drawable drawable, @ColorInt int lightColor,
+            @ColorInt int darkColor) {
+        return new TintedKeyButtonDrawable(new Drawable[] { drawable }, lightColor, darkColor);
+    }
+
+    private TintedKeyButtonDrawable(Drawable[] drawables, int lightColor, int darkColor){
+        super(drawables);
+        mLightColor = lightColor;
+        mDarkColor = darkColor;
+    }
+
+    @Override
+    public void setDarkIntensity(float intensity) {
+        // Duplicate intensity scaling from KeyButtonDrawable
+        int intermediateColor = ColorUtils.compositeColors(
+                setAlphaFloat(mDarkColor, intensity),
+                setAlphaFloat(mLightColor,1f - intensity));
+        getDrawable(0).setTint(intermediateColor);
+        invalidateSelf();
+    }
+
+    private int setAlphaFloat(int color, float alpha) {
+        return ColorUtils.setAlphaComponent(color, (int) (alpha * 255f));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index be28569..cd409d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -33,8 +33,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+
 import androidx.app.slice.SliceItem;
+import androidx.app.slice.SliceProvider;
+import androidx.app.slice.SliceSpecs;
 import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.widget.SliceLiveData;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -47,6 +52,7 @@
     public void setup() {
         mProvider = new TestableKeyguardSliceProvider();
         mProvider.attachInfo(getContext(), null);
+        SliceProvider.setSpecs(Arrays.asList(SliceSpecs.LIST));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 7f07e0c..3e37cfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.NotificationChannels;
 
+import java.util.concurrent.TimeUnit;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,6 +47,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class PowerNotificationWarningsTest extends SysuiTestCase {
+
+    public static final String FORMATTED_45M = "0h 45m";
+    public static final String FORMATTED_HOUR = "1h 0m";
     private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
     private PowerNotificationWarnings mPowerNotificationWarnings;
 
@@ -147,4 +151,22 @@
         verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(),
                 eq(SystemMessage.NOTE_THERMAL_SHUTDOWN), any());
     }
+
+    @Test
+    public void testGetTimeRemainingFormatted_roundsDownTo15() {
+        mPowerNotificationWarnings.updateEstimate(
+                new Estimate(TimeUnit.MINUTES.toMillis(57), true));
+        String time = mPowerNotificationWarnings.getTimeRemainingFormatted();
+
+        assertTrue("time:" + time + ", expected: " + FORMATTED_45M, time.equals(FORMATTED_45M));
+    }
+
+    @Test
+    public void testGetTimeRemainingFormatted_keepsMinutesWhenZero() {
+        mPowerNotificationWarnings.updateEstimate(
+                new Estimate(TimeUnit.MINUTES.toMillis(65), true));
+        String time = mPowerNotificationWarnings.getTimeRemainingFormatted();
+
+        assertTrue("time:" + time + ", expected: " + FORMATTED_HOUR, time.equals(FORMATTED_HOUR));
+    }
 }
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 e4734a4..fdb7f8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -19,12 +19,15 @@
 import static android.os.HardwarePropertiesManager.TEMPERATURE_SHUTDOWN;
 import static android.provider.Settings.Global.SHOW_TEMPERATURE_WARNING;
 
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.os.BatteryManager;
 import android.os.HardwarePropertiesManager;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
@@ -37,6 +40,7 @@
 import com.android.systemui.power.PowerUI.WarningsUI;
 import com.android.systemui.statusbar.phone.StatusBar;
 
+import java.util.concurrent.TimeUnit;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,14 +50,23 @@
 @SmallTest
 public class PowerUITest extends SysuiTestCase {
 
+    private static final boolean UNPLUGGED = false;
+    private static final boolean POWER_SAVER_OFF = false;
+    private static final int ABOVE_WARNING_BUCKET = 1;
+    public static final int BELOW_WARNING_BUCKET = -1;
+    public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2);
+    public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4);
     private HardwarePropertiesManager mHardProps;
     private WarningsUI mMockWarnings;
     private PowerUI mPowerUI;
+    private EnhancedEstimates mEnhacedEstimates;
 
     @Before
     public void setup() {
         mMockWarnings = mDependency.injectMockDependency(WarningsUI.class);
+        mEnhacedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class);
         mHardProps = mock(HardwarePropertiesManager.class);
+
         mContext.putComponent(StatusBar.class, mock(StatusBar.class));
         mContext.addMockSystemService(Context.HARDWARE_PROPERTIES_SERVICE, mHardProps);
 
@@ -128,6 +141,180 @@
         verify(mMockWarnings).showHighTemperatureWarning();
     }
 
+    @Test
+    public void testShouldShowLowBatteryWarning_showHybridOnly_returnsShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // unplugged device that would not show the non-hybrid notification but would show the
+        // hybrid
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        ABOVE_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD,
+                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertTrue(shouldShow);
+    }
+
+    @Test
+    public void testShouldShowLowBatteryWarning_showHybrid_showStandard_returnsShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // unplugged device that would show the non-hybrid notification and the hybrid
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD,
+                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertTrue(shouldShow);
+    }
+
+    @Test
+    public void testShouldShowLowBatteryWarning_showStandardOnly_returnsShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // unplugged device that would show the non-hybrid but not the hybrid
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, Long.MAX_VALUE, ABOVE_HYBRID_THRESHOLD,
+                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertTrue(shouldShow);
+    }
+
+    @Test
+    public void testShouldShowLowBatteryWarning_deviceHighBattery_returnsNoShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // unplugged device that would show the neither due to battery level being good
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, ABOVE_HYBRID_THRESHOLD,
+                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertFalse(shouldShow);
+    }
+
+    @Test
+    public void testShouldShowLowBatteryWarning_devicePlugged_returnsNoShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // plugged device that would show the neither due to being plugged
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(!UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD,
+                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertFalse(shouldShow);
+   }
+
+    @Test
+    public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnkown_returnsNoShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // Unknown battery status device that would show the neither due
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD,
+                        !POWER_SAVER_OFF, BatteryManager.BATTERY_STATUS_UNKNOWN);
+        assertFalse(shouldShow);
+    }
+
+    @Test
+    public void testShouldShowLowBatteryWarning_batterySaverEnabled_returnsNoShow() {
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        mPowerUI.start();
+
+        // BatterySaverEnabled device that would show the neither due to battery saver
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD,
+                        !POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertFalse(shouldShow);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_dismissWhenPowerSaverEnabled() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        // device that gets power saver turned on should dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, !POWER_SAVER_OFF);
+        assertTrue(shouldDismiss);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_dismissWhenPlugged() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+
+        // device that gets plugged in should dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(!UNPLUGGED, BELOW_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+        assertTrue(shouldDismiss);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_dismissHybridSignal_showStandardSignal_shouldShow() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        // would dismiss hybrid but not non-hybrid should not dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+        assertFalse(shouldDismiss);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_showHybridSignal_dismissStandardSignal_shouldShow() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+
+        // would dismiss non-hybrid but not hybrid should not dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+                        ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+        assertFalse(shouldDismiss);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_showBothSignal_shouldShow() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+
+        // should not dismiss when both would not dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+                        BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+        assertFalse(shouldDismiss);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_dismissBothSignal_shouldDismiss() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+
+        //should dismiss if both would dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+                        ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+        assertTrue(shouldDismiss);
+    }
+
+    @Test
+    public void testShouldDismissLowBatteryWarning_dismissStandardSignal_hybridDisabled_shouldDismiss() {
+        mPowerUI.start();
+        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(false);
+
+        // would dismiss non-hybrid with hybrid disabled should dismiss
+        boolean shouldDismiss =
+                mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+                        ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+        assertTrue(shouldDismiss);
+    }
+
     private void setCurrentTemp(float temp) {
         when(mHardProps.getDeviceTemperatures(DEVICE_TEMPERATURE_SKIN, TEMPERATURE_CURRENT))
                 .thenReturn(new float[] { temp });
diff --git a/packages/overlays/DisplayCutoutEmulationOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationOverlay/Android.mk
new file mode 100644
index 0000000..f4205ad6
--- /dev/null
+++ b/packages/overlays/DisplayCutoutEmulationOverlay/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_RRO_THEME := DisplayCutoutEmulation
+LOCAL_CERTIFICATE := platform
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := DisplayCutoutEmulationOverlay
+
+include $(BUILD_RRO_PACKAGE)
diff --git a/packages/overlays/DisplayCutoutEmulationOverlay/AndroidManifest.xml b/packages/overlays/DisplayCutoutEmulationOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..dd43690
--- /dev/null
+++ b/packages/overlays/DisplayCutoutEmulationOverlay/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.internal.display.cutout.emulation"
+    android:versionCode="1"
+    android:versionName="1.0">
+    <overlay android:targetPackage="android" android:priority="1"/>
+
+    <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/>
+</manifest>
diff --git a/packages/overlays/DisplayCutoutEmulationOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationOverlay/res/values/config.xml
new file mode 100644
index 0000000..88c19c7
--- /dev/null
+++ b/packages/overlays/DisplayCutoutEmulationOverlay/res/values/config.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ 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.
+  -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- The bounding path of the cutout region of the main built-in display.
+         Must either be empty if there is no cutout region, or a string that is parsable by
+         {@link android.util.PathParser}. -->
+    <string translatable="false" name="config_mainBuiltInDisplayCutout">
+        M 687.0,0
+        l -66,50
+        l 0,50
+        l 66,50
+        l 66,0
+        l 66,-50
+        l 0,-50
+        l -66,-50
+        z
+    </string>
+
+    <!-- Whether the display cutout region of the main built-in display should be forced to
+     black in software (to avoid aliasing or emulate a cutout that is not physically existent).
+     -->
+    <bool name="config_fillMainBuiltInDisplayCutout">true</bool>
+
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_height">150px</dimen>
+
+</resources>
+
+
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/packages/overlays/DisplayCutoutEmulationOverlay/res/values/strings.xml
similarity index 60%
copy from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
copy to packages/overlays/DisplayCutoutEmulationOverlay/res/values/strings.xml
index bd76051..5d5c425 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/packages/overlays/DisplayCutoutEmulationOverlay/res/values/strings.xml
@@ -1,11 +1,13 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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
+ *     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,
@@ -13,8 +15,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
-package android.security.recoverablekeystore;
+    <string name="display_cutout_emulation_overlay">Display Cutout Emulation</string>
 
-/* @hide */
-parcelable KeyStoreRecoveryData;
+</resources>
+
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 4f04d36..04cee67 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5129,6 +5129,21 @@
     // OS: P
     FUELGAUGE_SMART_BATTERY = 1281;
 
+    // ACTION: User tapped Screenshot in the power menu.
+    // CATEGORY: GLOBAL_SYSTEM_UI
+    // OS: P
+    ACTION_SCREENSHOT_POWER_MENU = 1282;
+
+    // OPEN: Settings > Apps & Notifications -> Special app access -> Storage Access
+    // CATEGORY: SETTINGS
+    // OS: P
+    STORAGE_ACCESS = 1283;
+
+    // OPEN: Settings > Apps & Notifications -> Special app access -> Storage Access -> Package
+    // CATEGORY: SETTINGS
+    // OS: P
+    APPLICATIONS_STORAGE_DETAIL = 1284;
+
     // ---- End P Constants, all P constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ba8ce59..fc6058c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1280,7 +1280,7 @@
         }
 
         int servicePackageUid = serviceInfo.applicationInfo.uid;
-        if (mAppOpsManager.noteOpNoThrow(AppOpsManager.OP_BIND_ACCESSIBILITY_SERVICE,
+        if (mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE,
                 servicePackageUid, serviceInfo.packageName) != AppOpsManager.MODE_ALLOWED) {
             Slog.w(LOG_TAG, "Skipping accessibility service " + new ComponentName(
                     serviceInfo.packageName, serviceInfo.name).flattenToShortString()
@@ -1362,14 +1362,13 @@
     private int computeRelevantEventTypes(UserState userState, Client client) {
         int relevantEventTypes = 0;
 
-        int numBoundServices = userState.mBoundServices.size();
-        for (int i = 0; i < numBoundServices; i++) {
-            AccessibilityServiceConnection service =
-                    userState.mBoundServices.get(i);
+        // Use iterator for thread-safety
+        for (AccessibilityServiceConnection service : userState.mBoundServices) {
             relevantEventTypes |= isClientInPackageWhitelist(service.getServiceInfo(), client)
                     ? service.getRelevantEventTypes()
                     : 0;
         }
+
         relevantEventTypes |= isClientInPackageWhitelist(
                 mUiAutomationManager.getServiceInfo(), client)
                 ? mUiAutomationManager.getRelevantEventTypes()
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 74d2ddd..0a03b7f 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -17,6 +17,7 @@
 package com.android.server.accessibility;
 
 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
@@ -364,7 +365,7 @@
 
                 persistScaleAndTransitionTo(mViewportDraggingState);
 
-            } else if (action == ACTION_UP) {
+            } else if (action == ACTION_UP || action == ACTION_CANCEL) {
 
                 persistScaleAndTransitionTo(mDetectingState);
 
@@ -496,7 +497,9 @@
                     }
                 }
                 break;
-                case ACTION_UP: {
+
+                case ACTION_UP:
+                case ACTION_CANCEL: {
                     if (!mZoomedInBeforeDrag) zoomOff();
                     clear();
                     transitionTo(mDetectingState);
@@ -533,12 +536,15 @@
 
         @Override
         public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-            if (event.getActionMasked() == ACTION_UP) {
-                transitionTo(mDetectingState);
-            }
+            switch (event.getActionMasked()) {
+                case ACTION_UP:
+                case ACTION_CANCEL: {
+                    transitionTo(mDetectingState);
+                } break;
 
-            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                mLastDelegatedDownEventTime = event.getDownTime();
+                case ACTION_DOWN: {
+                    mLastDelegatedDownEventTime = event.getDownTime();
+                } break;
             }
             if (getNext() != null) {
                 // We cache some events to see if the user wants to trigger magnification.
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 03591a8..350d7af 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -207,6 +207,10 @@
     public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
     public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
 
+    // Time delay for initialization operations that can be delayed so as not to consume too much CPU
+    // on bring-up and increase time-to-UI.
+    private static final long INITIALIZATION_DELAY_MILLIS = 3000;
+
     // Timeout interval for deciding that a bind or clear-data has taken too long
     private static final long TIMEOUT_INTERVAL = 10 * 1000;
 
@@ -282,6 +286,9 @@
 
     private final BackupPasswordManager mBackupPasswordManager;
 
+    // Time when we post the transport registration operation
+    private final long mRegisterTransportsRequestedTime;
+
     @GuardedBy("mPendingRestores")
     private boolean mIsRestoreInProgress;
     @GuardedBy("mPendingRestores")
@@ -735,6 +742,9 @@
         // Set up our transport options and initialize the default transport
         SystemConfig systemConfig = SystemConfig.getInstance();
         Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
+        if (transportWhitelist == null) {
+            transportWhitelist = Collections.emptySet();
+        }
 
         String transport =
                 Settings.Secure.getString(
@@ -749,8 +759,7 @@
                 new TransportManager(
                         context,
                         transportWhitelist,
-                        transport,
-                        backupThread.getLooper());
+                        transport);
 
         // If encrypted file systems is enabled or disabled, this call will return the
         // correct directory.
@@ -852,12 +861,14 @@
         }
 
         mTransportManager = transportManager;
-        mTransportManager.setTransportBoundListener(mTransportBoundListener);
-        mTransportManager.registerAllTransports();
+        mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered);
+        mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime();
+        mBackupHandler.postDelayed(
+                mTransportManager::registerTransports, INITIALIZATION_DELAY_MILLIS);
 
-        // Now that we know about valid backup participants, parse any
-        // leftover journal files into the pending backup set
-        mBackupHandler.post(this::parseLeftoverJournals);
+        // Now that we know about valid backup participants, parse any leftover journal files into
+        // the pending backup set
+        mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);
 
         // Power management
         mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
@@ -1151,39 +1162,28 @@
         }
     }
 
-    private TransportManager.TransportBoundListener mTransportBoundListener =
-            new TransportManager.TransportBoundListener() {
-                @Override
-                public boolean onTransportBound(IBackupTransport transport) {
-                    // If the init sentinel file exists, we need to be sure to perform the init
-                    // as soon as practical.  We also create the state directory at registration
-                    // time to ensure it's present from the outset.
-                    String name = null;
-                    try {
-                        name = transport.name();
-                        String transportDirName = transport.transportDirName();
-                        File stateDir = new File(mBaseStateDir, transportDirName);
-                        stateDir.mkdirs();
+    private void onTransportRegistered(String transportName, String transportDirName) {
+        if (DEBUG) {
+            long timeMs = SystemClock.elapsedRealtime() - mRegisterTransportsRequestedTime;
+            Slog.d(TAG, "Transport " + transportName + " registered " + timeMs
+                    + "ms after first request (delay = " + INITIALIZATION_DELAY_MILLIS + "ms)");
+        }
 
-                        File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
-                        if (initSentinel.exists()) {
-                            synchronized (mQueueLock) {
-                                mPendingInits.add(name);
+        File stateDir = new File(mBaseStateDir, transportDirName);
+        stateDir.mkdirs();
 
-                                // TODO: pick a better starting time than now + 1 minute
-                                long delay = 1000 * 60; // one minute, in milliseconds
-                                mAlarmManager.set(AlarmManager.RTC_WAKEUP,
-                                        System.currentTimeMillis() + delay, mRunInitIntent);
-                            }
-                        }
-                        return true;
-                    } catch (Exception e) {
-                        // the transport threw when asked its file naming prefs; declare it invalid
-                        Slog.w(TAG, "Failed to regiser transport: " + name);
-                        return false;
-                    }
-                }
-            };
+        File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+        if (initSentinel.exists()) {
+            synchronized (mQueueLock) {
+                mPendingInits.add(transportName);
+
+                // TODO: pick a better starting time than now + 1 minute
+                long delay = 1000 * 60; // one minute, in milliseconds
+                mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+                        System.currentTimeMillis() + delay, mRunInitIntent);
+            }
+        }
+    }
 
     // ----- Track installation/removal of packages -----
     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -2891,14 +2891,14 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "listAllTransports");
 
-        return mTransportManager.getBoundTransportNames();
+        return mTransportManager.getRegisteredTransportNames();
     }
 
     @Override
     public ComponentName[] listAllTransportComponents() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "listAllTransportComponents");
-        return mTransportManager.getAllTransportComponents();
+        return mTransportManager.getRegisteredTransportComponents();
     }
 
     @Override
@@ -3003,6 +3003,8 @@
 
     /** Selects transport {@code transportName} and returns previous selected transport. */
     @Override
+    @Deprecated
+    @Nullable
     public String selectBackupTransport(String transportName) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BACKUP, "selectBackupTransport");
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 34b8935..09456b68 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -16,93 +16,56 @@
 
 package com.android.server.backup;
 
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-
 import android.annotation.Nullable;
+import android.annotation.WorkerThread;
 import android.app.backup.BackupManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.EventLog;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.backup.IBackupTransport;
+import com.android.internal.util.Preconditions;
 import com.android.server.EventLogTags;
+import com.android.server.backup.transport.OnTransportRegisteredListener;
 import com.android.server.backup.transport.TransportClient;
 import com.android.server.backup.transport.TransportClientManager;
 import com.android.server.backup.transport.TransportConnectionListener;
 import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.transport.TransportNotRegisteredException;
 
-import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
-/**
- * Handles in-memory bookkeeping of all BackupTransport objects.
- */
+/** Handles in-memory bookkeeping of all BackupTransport objects. */
 public class TransportManager {
-
     private static final String TAG = "BackupTransportManager";
 
     @VisibleForTesting
     public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
 
-    private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec
-    private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins
-    private static final int REBINDING_TIMEOUT_MSG = 1;
-
     private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final Set<ComponentName> mTransportWhitelist;
-    private final Handler mHandler;
     private final TransportClientManager mTransportClientManager;
-
-    /**
-     * This listener is called after we bind to any transport. If it returns true, this is a valid
-     * transport.
-     */
-    private TransportBoundListener mTransportBoundListener;
-
     private final Object mTransportLock = new Object();
-
-    /**
-     * We have detected these transports on the device. Unless in exceptional cases, we are also
-     * bound to all of these.
-     */
-    @GuardedBy("mTransportLock")
-    private final Map<ComponentName, TransportConnection> mValidTransports = new ArrayMap<>();
-
-    /** We are currently bound to these transports. */
-    @GuardedBy("mTransportLock")
-    private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
-
-    /** @see #getEligibleTransportComponents() */
-    @GuardedBy("mTransportLock")
-    private final Set<ComponentName> mEligibleTransports = new ArraySet<>();
+    private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {};
 
     /** @see #getRegisteredTransportNames() */
     @GuardedBy("mTransportLock")
@@ -110,120 +73,98 @@
             new ArrayMap<>();
 
     @GuardedBy("mTransportLock")
+    @Nullable
     private volatile String mCurrentTransportName;
 
-    TransportManager(
-            Context context,
-            Set<ComponentName> whitelist,
-            String defaultTransport,
-            TransportBoundListener listener,
-            Looper looper) {
-        this(context, whitelist, defaultTransport, looper);
-        mTransportBoundListener = listener;
+    TransportManager(Context context, Set<ComponentName> whitelist, String selectedTransport) {
+        this(context, whitelist, selectedTransport, new TransportClientManager(context));
     }
 
+    @VisibleForTesting
     TransportManager(
             Context context,
             Set<ComponentName> whitelist,
-            String defaultTransport,
-            Looper looper) {
+            String selectedTransport,
+            TransportClientManager transportClientManager) {
         mContext = context;
         mPackageManager = context.getPackageManager();
-        if (whitelist != null) {
-            mTransportWhitelist = whitelist;
-        } else {
-            mTransportWhitelist = new ArraySet<>();
-        }
-        mCurrentTransportName = defaultTransport;
-        mHandler = new RebindOnTimeoutHandler(looper);
-        mTransportClientManager = new TransportClientManager(context);
+        mTransportWhitelist = Preconditions.checkNotNull(whitelist);
+        mCurrentTransportName = selectedTransport;
+        mTransportClientManager = transportClientManager;
     }
 
-    public void setTransportBoundListener(TransportBoundListener transportBoundListener) {
-        mTransportBoundListener = transportBoundListener;
+    /* Sets a listener to be called whenever a transport is registered. */
+    public void setOnTransportRegisteredListener(OnTransportRegisteredListener listener) {
+        mOnTransportRegisteredListener = listener;
     }
 
+    @WorkerThread
     void onPackageAdded(String packageName) {
-        // New package added. Bind to all transports it contains.
-        synchronized (mTransportLock) {
-            log_verbose("Package added. Binding to all transports. " + packageName);
-            bindToAllInternal(packageName, null /* all components */);
-        }
+        registerTransportsFromPackage(packageName, transportComponent -> true);
     }
 
     void onPackageRemoved(String packageName) {
-        // Package removed. Remove all its transports from our list. These transports have already
-        // been removed from mBoundTransports because onServiceDisconnected would already been
-        // called on TransportConnection objects.
         synchronized (mTransportLock) {
-            Iterator<Map.Entry<ComponentName, TransportConnection>> iter =
-                    mValidTransports.entrySet().iterator();
-            while (iter.hasNext()) {
-                Map.Entry<ComponentName, TransportConnection> validTransport = iter.next();
-                ComponentName componentName = validTransport.getKey();
-                if (componentName.getPackageName().equals(packageName)) {
-                    TransportConnection transportConnection = validTransport.getValue();
-                    iter.remove();
-                    if (transportConnection != null) {
-                        mContext.unbindService(transportConnection);
-                        log_verbose("Package removed, removing transport: "
-                                + componentName.flattenToShortString());
-                    }
-                }
-            }
-            removeTransportsIfLocked(
-                    componentName -> packageName.equals(componentName.getPackageName()));
+            mRegisteredTransportsDescriptionMap.keySet().removeIf(fromPackageFilter(packageName));
         }
     }
 
-    void onPackageChanged(String packageName, String[] components) {
+    @WorkerThread
+    void onPackageChanged(String packageName, String... components) {
         synchronized (mTransportLock) {
-            // Remove all changed components from mValidTransports. We'll bind to them again
-            // and re-add them if still valid.
-            Set<ComponentName> transportsToBeRemoved = new ArraySet<>();
-            for (String component : components) {
-                ComponentName componentName = new ComponentName(packageName, component);
-                transportsToBeRemoved.add(componentName);
-                TransportConnection removed = mValidTransports.remove(componentName);
-                if (removed != null) {
-                    mContext.unbindService(removed);
-                    log_verbose("Package changed. Removing transport: " +
-                            componentName.flattenToShortString());
-                }
-            }
-            removeTransportsIfLocked(transportsToBeRemoved::contains);
-            bindToAllInternal(packageName, components);
+            Set<ComponentName> transportComponents =
+                    Stream.of(components)
+                            .map(component -> new ComponentName(packageName, component))
+                            .collect(Collectors.toSet());
+
+            mRegisteredTransportsDescriptionMap.keySet().removeIf(transportComponents::contains);
+            registerTransportsFromPackage(packageName, transportComponents::contains);
         }
     }
 
-    @GuardedBy("mTransportLock")
-    private void removeTransportsIfLocked(Predicate<ComponentName> filter) {
-        mEligibleTransports.removeIf(filter);
-        mRegisteredTransportsDescriptionMap.keySet().removeIf(filter);
-    }
-
-    public IBackupTransport getTransportBinder(String transportName) {
+    /**
+     * Returns the {@link ComponentName}s of the registered transports.
+     *
+     * <p>A *registered* transport is a transport that satisfies intent with action
+     * android.backup.TRANSPORT_HOST, returns true for {@link #isTransportTrusted(ComponentName)}
+     * and that we have successfully connected to once.
+     */
+    ComponentName[] getRegisteredTransportComponents() {
         synchronized (mTransportLock) {
-            ComponentName component = mBoundTransports.get(transportName);
-            if (component == null) {
-                Slog.w(TAG, "Transport " + transportName + " not bound.");
-                return null;
-            }
-            TransportConnection conn = mValidTransports.get(component);
-            if (conn == null) {
-                Slog.w(TAG, "Transport " + transportName + " not valid.");
-                return null;
-            }
-            return conn.getBinder();
+            return mRegisteredTransportsDescriptionMap
+                    .keySet()
+                    .toArray(new ComponentName[mRegisteredTransportsDescriptionMap.size()]);
         }
     }
 
-    public IBackupTransport getCurrentTransportBinder() {
-        return getTransportBinder(mCurrentTransportName);
+    /**
+     * Returns the names of the registered transports.
+     *
+     * @see #getRegisteredTransportComponents()
+     */
+    String[] getRegisteredTransportNames() {
+        synchronized (mTransportLock) {
+            return mRegisteredTransportsDescriptionMap
+                    .values()
+                    .stream()
+                    .map(transportDescription -> transportDescription.name)
+                    .toArray(String[]::new);
+        }
+    }
+
+    /** Returns a set with the whitelisted transports. */
+    Set<ComponentName> getTransportWhitelist() {
+        return mTransportWhitelist;
+    }
+
+    @Nullable
+    String getCurrentTransportName() {
+        return mCurrentTransportName;
     }
 
     /**
      * Returns the transport name associated with {@code transportComponent}.
+     *
      * @throws TransportNotRegisteredException if the transport is not registered.
      */
     public String getTransportName(ComponentName transportComponent)
@@ -234,7 +175,32 @@
     }
 
     /**
-     * Retrieve the configuration intent of {@code transportName}.
+     * Retrieves the transport dir name of {@code transportComponent}.
+     *
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public String getTransportDirName(ComponentName transportComponent)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportComponent)
+                    .transportDirName;
+        }
+    }
+
+    /**
+     * Retrieves the transport dir name of {@code transportName}.
+     *
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public String getTransportDirName(String transportName) throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportName).transportDirName;
+        }
+    }
+
+    /**
+     * Retrieves the configuration intent of {@code transportName}.
+     *
      * @throws TransportNotRegisteredException if the transport is not registered.
      */
     @Nullable
@@ -247,7 +213,21 @@
     }
 
     /**
-     * Retrieve the data management intent of {@code transportName}.
+     * Retrieves the current destination string of {@code transportName}.
+     *
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public String getTransportCurrentDestinationString(String transportName)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
+                    .currentDestinationString;
+        }
+    }
+
+    /**
+     * Retrieves the data management intent of {@code transportName}.
+     *
      * @throws TransportNotRegisteredException if the transport is not registered.
      */
     @Nullable
@@ -260,19 +240,8 @@
     }
 
     /**
-     * Retrieve the current destination string of {@code transportName}.
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    public String getTransportCurrentDestinationString(String transportName)
-            throws TransportNotRegisteredException {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
-                    .currentDestinationString;
-        }
-    }
-
-    /**
-     * Retrieve the data management label of {@code transportName}.
+     * Retrieves the data management label of {@code transportName}.
+     *
      * @throws TransportNotRegisteredException if the transport is not registered.
      */
     @Nullable
@@ -284,54 +253,74 @@
         }
     }
 
-    /**
-     * Retrieve the transport dir name of {@code transportName}.
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    public String getTransportDirName(String transportName)
-            throws TransportNotRegisteredException {
+    /* Returns true if the transport identified by {@code transportName} is registered. */
+    public boolean isTransportRegistered(String transportName) {
         synchronized (mTransportLock) {
-            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
-                    .transportDirName;
-        }
-    }
-
-    /**
-     * Retrieve the transport dir name of {@code transportComponent}.
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    public String getTransportDirName(ComponentName transportComponent)
-            throws TransportNotRegisteredException {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportDescriptionOrThrowLocked(transportComponent)
-                    .transportDirName;
+            return getRegisteredTransportEntryLocked(transportName) != null;
         }
     }
 
     /**
      * Execute {@code transportConsumer} for each registered transport passing the transport name.
      * This is called with an internal lock held, ensuring that the transport will remain registered
-     * while {@code transportConsumer} is being executed. Don't do heavy operations in
-     * {@code transportConsumer}.
+     * while {@code transportConsumer} is being executed. Don't do heavy operations in {@code
+     * transportConsumer}.
      */
     public void forEachRegisteredTransport(Consumer<String> transportConsumer) {
         synchronized (mTransportLock) {
-            for (TransportDescription transportDescription
-                    : mRegisteredTransportsDescriptionMap.values()) {
+            for (TransportDescription transportDescription :
+                    mRegisteredTransportsDescriptionMap.values()) {
                 transportConsumer.accept(transportDescription.name);
             }
         }
     }
 
-    public String getTransportName(IBackupTransport binder) {
+    /**
+     * Updates given values for the transport already registered and identified with {@param
+     * transportComponent}. If the transport is not registered it will log and return.
+     */
+    public void updateTransportAttributes(
+            ComponentName transportComponent,
+            String name,
+            @Nullable Intent configurationIntent,
+            String currentDestinationString,
+            @Nullable Intent dataManagementIntent,
+            @Nullable String dataManagementLabel) {
         synchronized (mTransportLock) {
-            for (TransportConnection conn : mValidTransports.values()) {
-                if (conn.getBinder() == binder) {
-                    return conn.getName();
-                }
+            TransportDescription description =
+                    mRegisteredTransportsDescriptionMap.get(transportComponent);
+            if (description == null) {
+                Slog.e(TAG, "Transport " + name + " not registered tried to change description");
+                return;
             }
+            description.name = name;
+            description.configurationIntent = configurationIntent;
+            description.currentDestinationString = currentDestinationString;
+            description.dataManagementIntent = dataManagementIntent;
+            description.dataManagementLabel = dataManagementLabel;
+            Slog.d(TAG, "Transport " + name + " updated its attributes");
         }
-        return null;
+    }
+
+    @GuardedBy("mTransportLock")
+    private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
+            ComponentName transportComponent) throws TransportNotRegisteredException {
+        TransportDescription description =
+                mRegisteredTransportsDescriptionMap.get(transportComponent);
+        if (description == null) {
+            throw new TransportNotRegisteredException(transportComponent);
+        }
+        return description;
+    }
+
+    @GuardedBy("mTransportLock")
+    private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
+            String transportName) throws TransportNotRegisteredException {
+        TransportDescription description = getRegisteredTransportDescriptionLocked(transportName);
+        if (description == null) {
+            throw new TransportNotRegisteredException(transportName);
+        }
+        return description;
     }
 
     @GuardedBy("mTransportLock")
@@ -351,21 +340,11 @@
     }
 
     @GuardedBy("mTransportLock")
-    private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
-            String transportName) throws TransportNotRegisteredException {
-        TransportDescription description = getRegisteredTransportDescriptionLocked(transportName);
-        if (description == null) {
-            throw new TransportNotRegisteredException(transportName);
-        }
-        return description;
-    }
-
-    @GuardedBy("mTransportLock")
     @Nullable
     private Map.Entry<ComponentName, TransportDescription> getRegisteredTransportEntryLocked(
             String transportName) {
-        for (Map.Entry<ComponentName, TransportDescription> entry
-                : mRegisteredTransportsDescriptionMap.entrySet()) {
+        for (Map.Entry<ComponentName, TransportDescription> entry :
+                mRegisteredTransportsDescriptionMap.entrySet()) {
             TransportDescription description = entry.getValue();
             if (transportName.equals(description.name)) {
                 return entry;
@@ -374,17 +353,16 @@
         return null;
     }
 
-    @GuardedBy("mTransportLock")
-    private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
-            ComponentName transportComponent) throws TransportNotRegisteredException {
-        TransportDescription description =
-                mRegisteredTransportsDescriptionMap.get(transportComponent);
-        if (description == null) {
-            throw new TransportNotRegisteredException(transportComponent);
-        }
-        return description;
-    }
-
+    /**
+     * Returns a {@link TransportClient} for {@code transportName} or {@code null} if not
+     * registered.
+     *
+     * @param transportName The name of the transport.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient} or null if not registered.
+     */
     @Nullable
     public TransportClient getTransportClient(String transportName, String caller) {
         try {
@@ -395,6 +373,16 @@
         }
     }
 
+    /**
+     * Returns a {@link TransportClient} for {@code transportName} or throws if not registered.
+     *
+     * @param transportName The name of the transport.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient}.
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
     public TransportClient getTransportClientOrThrow(String transportName, String caller)
             throws TransportNotRegisteredException {
         synchronized (mTransportLock) {
@@ -406,19 +394,14 @@
         }
     }
 
-    public boolean isTransportRegistered(String transportName) {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportEntryLocked(transportName) != null;
-        }
-    }
-
     /**
-     * Returns a {@link TransportClient} for the current transport or null if not found.
+     * Returns a {@link TransportClient} for the current transport or {@code null} if not
+     * registered.
      *
      * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
      *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
      *     details.
-     * @return A {@link TransportClient} or null if not found.
+     * @return A {@link TransportClient} or null if not registered.
      */
     @Nullable
     public TransportClient getCurrentTransportClient(String caller) {
@@ -455,130 +438,88 @@
         mTransportClientManager.disposeOfTransportClient(transportClient, caller);
     }
 
-    String[] getBoundTransportNames() {
-        synchronized (mTransportLock) {
-            return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]);
-        }
-    }
-
-    ComponentName[] getAllTransportComponents() {
-        synchronized (mTransportLock) {
-            return mValidTransports.keySet().toArray(new ComponentName[mValidTransports.size()]);
-        }
-    }
-
     /**
-     * An *eligible* transport is a service component that satisfies intent with action
-     * android.backup.TRANSPORT_HOST and returns true for
-     * {@link #isTransportTrusted(ComponentName)}. It may be registered or not registered.
-     * This method returns the {@link ComponentName}s of those transports.
-     */
-    ComponentName[] getEligibleTransportComponents() {
-        synchronized (mTransportLock) {
-            return mEligibleTransports.toArray(new ComponentName[mEligibleTransports.size()]);
-        }
-    }
-
-    Set<ComponentName> getTransportWhitelist() {
-        return mTransportWhitelist;
-    }
-
-    /**
-     * A *registered* transport is an eligible transport that has been successfully connected and
-     * that returned true for method
-     * {@link TransportBoundListener#onTransportBound(IBackupTransport)} of TransportBoundListener
-     * provided in the constructor. This method returns the names of the registered transports.
-     */
-    String[] getRegisteredTransportNames() {
-        synchronized (mTransportLock) {
-            return mRegisteredTransportsDescriptionMap.values().stream()
-                    .map(transportDescription -> transportDescription.name)
-                    .toArray(String[]::new);
-        }
-    }
-
-    /**
-     * Updates given values for the transport already registered and identified with
-     * {@param transportComponent}. If the transport is not registered it will log and return.
-     */
-    public void updateTransportAttributes(
-            ComponentName transportComponent,
-            String name,
-            @Nullable Intent configurationIntent,
-            String currentDestinationString,
-            @Nullable Intent dataManagementIntent,
-            @Nullable String dataManagementLabel) {
-        synchronized (mTransportLock) {
-            TransportDescription description =
-                    mRegisteredTransportsDescriptionMap.get(transportComponent);
-            if (description == null) {
-                Slog.e(TAG, "Transport " + name + " not registered tried to change description");
-                return;
-            }
-            description.name = name;
-            description.configurationIntent = configurationIntent;
-            description.currentDestinationString = currentDestinationString;
-            description.dataManagementIntent = dataManagementIntent;
-            description.dataManagementLabel = dataManagementLabel;
-            Slog.d(TAG, "Transport " + name + " updated its attributes");
-        }
-    }
-
-    @Nullable
-    String getCurrentTransportName() {
-        return mCurrentTransportName;
-    }
-
-    // This is for mocking, Mockito can't mock if package-protected and in the same package but
-    // different class loaders. Checked with the debugger and class loaders are different
-    // See https://github.com/mockito/mockito/issues/796
-    @VisibleForTesting(visibility = PACKAGE)
-    public void registerAllTransports() {
-        bindToAllInternal(null /* all packages */, null /* all components */);
-    }
-
-    /**
-     * Bind to all transports belonging to the given package and the given component list.
-     * null acts a wildcard.
+     * Sets {@code transportName} as selected transport and returns previously selected transport
+     * name. If there was no previous transport it returns null.
      *
-     * If packageName is null, bind to all transports in all packages.
-     * If components is null, bind to all transports in the given package.
+     * <p>You should NOT call this method in new code. This won't make any checks against {@code
+     * transportName}, putting any operation at risk of a {@link TransportNotRegisteredException} or
+     * another error at the time it's being executed.
+     *
+     * <p>{@link Deprecated} as public, this method can be used as private.
      */
-    private void bindToAllInternal(String packageName, String[] components) {
-        PackageInfo pkgInfo = null;
-        if (packageName != null) {
+    @Deprecated
+    @Nullable
+    String selectTransport(String transportName) {
+        synchronized (mTransportLock) {
+            String prevTransport = mCurrentTransportName;
+            mCurrentTransportName = transportName;
+            return prevTransport;
+        }
+    }
+
+    /**
+     * Tries to register the transport if not registered. If successful also selects the transport.
+     *
+     * @param transportComponent Host of the transport.
+     * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
+     *     or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
+     */
+    @WorkerThread
+    public int registerAndSelectTransport(ComponentName transportComponent) {
+        synchronized (mTransportLock) {
+            if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) {
+                int result = registerTransport(transportComponent);
+                if (result != BackupManager.SUCCESS) {
+                    return result;
+                }
+            }
+
             try {
-                pkgInfo = mPackageManager.getPackageInfo(packageName, 0);
-            } catch (PackageManager.NameNotFoundException e) {
-                Slog.w(TAG, "Package not found: " + packageName);
-                return;
+                selectTransport(getTransportName(transportComponent));
+                return BackupManager.SUCCESS;
+            } catch (TransportNotRegisteredException e) {
+                // Shouldn't happen because we are holding the lock
+                Slog.wtf(TAG, "Transport unexpectedly not registered");
+                return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
             }
         }
+    }
 
-        Intent intent = new Intent(mTransportServiceIntent);
-        if (packageName != null) {
-            intent.setPackage(packageName);
+    @WorkerThread
+    public void registerTransports() {
+        registerTransportsForIntent(mTransportServiceIntent, transportComponent -> true);
+    }
+
+    @WorkerThread
+    private void registerTransportsFromPackage(
+            String packageName, Predicate<ComponentName> transportComponentFilter) {
+        try {
+            mPackageManager.getPackageInfo(packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "Trying to register transports from package not found " + packageName);
+            return;
         }
 
-        List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
-                intent, 0, UserHandle.USER_SYSTEM);
-        if (hosts != null) {
+        registerTransportsForIntent(
+                new Intent(mTransportServiceIntent).setPackage(packageName),
+                transportComponentFilter.and(fromPackageFilter(packageName)));
+    }
+
+    @WorkerThread
+    private void registerTransportsForIntent(
+            Intent intent, Predicate<ComponentName> transportComponentFilter) {
+        List<ResolveInfo> hosts =
+                mPackageManager.queryIntentServicesAsUser(intent, 0, UserHandle.USER_SYSTEM);
+        if (hosts == null) {
+            return;
+        }
+        synchronized (mTransportLock) {
             for (ResolveInfo host : hosts) {
-                final ComponentName infoComponentName = getComponentName(host.serviceInfo);
-                boolean shouldBind = false;
-                if (components != null && packageName != null) {
-                    for (String component : components) {
-                        ComponentName cn = new ComponentName(pkgInfo.packageName, component);
-                        if (infoComponentName.equals(cn)) {
-                            shouldBind = true;
-                            break;
-                        }
-                    }
-                } else {
-                    shouldBind = true;
-                }
-                if (shouldBind && isTransportTrusted(infoComponentName)) {
-                    tryBindTransport(infoComponentName);
+                ComponentName transportComponent = host.serviceInfo.getComponentName();
+                if (transportComponentFilter.test(transportComponent)
+                        && isTransportTrusted(transportComponent)) {
+                    registerTransport(transportComponent);
                 }
             }
         }
@@ -605,64 +546,6 @@
         return true;
     }
 
-    private void tryBindTransport(ComponentName transportComponentName) {
-        Slog.d(TAG, "Binding to transport: " + transportComponentName.flattenToShortString());
-        // TODO: b/22388012 (Multi user backup and restore)
-        TransportConnection connection = new TransportConnection(transportComponentName);
-        synchronized (mTransportLock) {
-            mEligibleTransports.add(transportComponentName);
-        }
-        if (bindToTransport(transportComponentName, connection)) {
-            synchronized (mTransportLock) {
-                mValidTransports.put(transportComponentName, connection);
-            }
-        } else {
-            Slog.w(TAG, "Couldn't bind to transport " + transportComponentName);
-        }
-    }
-
-    private boolean bindToTransport(ComponentName componentName, ServiceConnection connection) {
-        Intent intent = new Intent(mTransportServiceIntent)
-                .setComponent(componentName);
-        return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
-                createSystemUserHandle());
-    }
-
-    String selectTransport(String transportName) {
-        synchronized (mTransportLock) {
-            String prevTransport = mCurrentTransportName;
-            mCurrentTransportName = transportName;
-            return prevTransport;
-        }
-    }
-
-    /**
-     * Tries to register the transport if not registered. If successful also selects the transport.
-     *
-     * @param transportComponent Host of the transport.
-     * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
-     *     or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
-     */
-    public int registerAndSelectTransport(ComponentName transportComponent) {
-        synchronized (mTransportLock) {
-            if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) {
-                int result = registerTransport(transportComponent);
-                if (result != BackupManager.SUCCESS) {
-                    return result;
-                }
-            }
-
-            try {
-                selectTransport(getTransportName(transportComponent));
-                return BackupManager.SUCCESS;
-            } catch (TransportNotRegisteredException e) {
-                // Shouldn't happen because we are holding the lock
-                Slog.wtf(TAG, "Transport unexpectedly not registered");
-                return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
-            }
-        }
-    }
-
     /**
      * Tries to register transport represented by {@code transportComponent}.
      *
@@ -670,7 +553,12 @@
      * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
      *     or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
      */
+    @WorkerThread
     private int registerTransport(ComponentName transportComponent) {
+        if (!isTransportTrusted(transportComponent)) {
+            return BackupManager.ERROR_TRANSPORT_INVALID;
+        }
+
         String transportString = transportComponent.flattenToShortString();
 
         String callerLogString = "TransportManager.registerTransport()";
@@ -689,26 +577,21 @@
         EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 1);
 
         int result;
-        if (isTransportValid(transport)) {
-            try {
-                registerTransport(transportComponent, transport);
-                // If registerTransport() hasn't thrown...
-                result = BackupManager.SUCCESS;
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Transport " + transportString + " died while registering");
-                result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
-            }
-        } else {
-            Slog.w(TAG, "Can't register invalid transport " + transportString);
-            result = BackupManager.ERROR_TRANSPORT_INVALID;
+        try {
+            String transportName = transport.name();
+            String transportDirName = transport.transportDirName();
+            registerTransport(transportComponent, transport);
+            // If registerTransport() hasn't thrown...
+            Slog.d(TAG, "Transport " + transportString + " registered");
+            mOnTransportRegisteredListener.onTransportRegistered(transportName, transportDirName);
+            result = BackupManager.SUCCESS;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Transport " + transportString + " died while registering");
+            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 0);
+            result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
         }
 
         mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString);
-        if (result == BackupManager.SUCCESS) {
-            Slog.d(TAG, "Transport " + transportString + " registered");
-        } else {
-            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 0);
-        }
         return result;
     }
 
@@ -717,204 +600,20 @@
             throws RemoteException {
         synchronized (mTransportLock) {
             String name = transport.name();
-            TransportDescription description = new TransportDescription(
-                    name,
-                    transport.transportDirName(),
-                    transport.configurationIntent(),
-                    transport.currentDestinationString(),
-                    transport.dataManagementIntent(),
-                    transport.dataManagementLabel());
+            TransportDescription description =
+                    new TransportDescription(
+                            name,
+                            transport.transportDirName(),
+                            transport.configurationIntent(),
+                            transport.currentDestinationString(),
+                            transport.dataManagementIntent(),
+                            transport.dataManagementLabel());
             mRegisteredTransportsDescriptionMap.put(transportComponent, description);
         }
     }
 
-    private boolean isTransportValid(IBackupTransport transport) {
-        if (mTransportBoundListener == null) {
-            Slog.w(TAG, "setTransportBoundListener() not called, assuming transport invalid");
-            return false;
-        }
-        return mTransportBoundListener.onTransportBound(transport);
-    }
-
-    private class TransportConnection implements ServiceConnection {
-
-        // Hold mTransportLock to access these fields so as to provide a consistent view of them.
-        private volatile IBackupTransport mBinder;
-        private volatile String mTransportName;
-
-        private final ComponentName mTransportComponent;
-
-        private TransportConnection(ComponentName transportComponent) {
-            mTransportComponent = transportComponent;
-        }
-
-        @Override
-        public void onServiceConnected(ComponentName component, IBinder binder) {
-            synchronized (mTransportLock) {
-                mBinder = IBackupTransport.Stub.asInterface(binder);
-                boolean success = false;
-
-                EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
-                        component.flattenToShortString(), 1);
-
-                try {
-                    mTransportName = mBinder.name();
-                    // BackupManager requests some fields from the transport. If they are
-                    // invalid, throw away this transport.
-                    if (isTransportValid(mBinder)) {
-                        // We're now using the always-bound connection to do the registration but
-                        // when we remove the always-bound code this will be in the first binding
-                        // TODO: Move registration to first binding
-                        registerTransport(component, mBinder);
-                        // If registerTransport() hasn't thrown...
-                        success = true;
-                    }
-                } catch (RemoteException e) {
-                    success = false;
-                    Slog.e(TAG, "Couldn't get transport name.", e);
-                } finally {
-                    // we need to intern() the String of the component, so that we can use it with
-                    // Handler's removeMessages(), which uses == operator to compare the tokens
-                    String componentShortString = component.flattenToShortString().intern();
-                    if (success) {
-                        Slog.d(TAG, "Bound to transport: " + componentShortString);
-                        mBoundTransports.put(mTransportName, component);
-                        // cancel rebinding on timeout for this component as we've already connected
-                        mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
-                    } else {
-                        Slog.w(TAG, "Bound to transport " + componentShortString +
-                                " but it is invalid");
-                        EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
-                                componentShortString, 0);
-                        mContext.unbindService(this);
-                        mValidTransports.remove(component);
-                        mEligibleTransports.remove(component);
-                        mBinder = null;
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName component) {
-            synchronized (mTransportLock) {
-                mBinder = null;
-                mBoundTransports.remove(mTransportName);
-            }
-            String componentShortString = component.flattenToShortString();
-            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, componentShortString, 0);
-            Slog.w(TAG, "Disconnected from transport " + componentShortString);
-            scheduleRebindTimeout(component);
-        }
-
-        /**
-         * We'll attempt to explicitly rebind to a transport if it hasn't happened automatically
-         * for a few minutes after the binding went away.
-         */
-        private void scheduleRebindTimeout(ComponentName component) {
-            // we need to intern() the String of the component, so that we can use it with Handler's
-            // removeMessages(), which uses == operator to compare the tokens
-            final String componentShortString = component.flattenToShortString().intern();
-            final long rebindTimeout = getRebindTimeout();
-            mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
-            Message msg = mHandler.obtainMessage(REBINDING_TIMEOUT_MSG);
-            msg.obj = componentShortString;
-            mHandler.sendMessageDelayed(msg, rebindTimeout);
-            Slog.d(TAG, "Scheduled explicit rebinding for " + componentShortString + " in "
-                    + rebindTimeout + "ms");
-        }
-
-        // Intentionally not synchronized -- the variable is volatile and changes to its value
-        // are inside synchronized blocks, providing a memory sync barrier; and this method
-        // does not touch any other state protected by that lock.
-        private IBackupTransport getBinder() {
-            return mBinder;
-        }
-
-        // Intentionally not synchronized; same as getBinder()
-        private String getName() {
-            return mTransportName;
-        }
-
-        // Intentionally not synchronized; same as getBinder()
-        private void bindIfUnbound() {
-            if (mBinder == null) {
-                Slog.d(TAG,
-                        "Rebinding to transport " + mTransportComponent.flattenToShortString());
-                bindToTransport(mTransportComponent, this);
-            }
-        }
-
-        private long getRebindTimeout() {
-            final boolean isDeviceProvisioned = Settings.Global.getInt(
-                    mContext.getContentResolver(),
-                    Settings.Global.DEVICE_PROVISIONED, 0) != 0;
-            return isDeviceProvisioned
-                    ? REBINDING_TIMEOUT_PROVISIONED_MS
-                    : REBINDING_TIMEOUT_UNPROVISIONED_MS;
-        }
-    }
-
-    public interface TransportBoundListener {
-        /** Should return true if this is a valid transport. */
-        boolean onTransportBound(IBackupTransport binder);
-    }
-
-    private class RebindOnTimeoutHandler extends Handler {
-
-        RebindOnTimeoutHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == REBINDING_TIMEOUT_MSG) {
-                String componentShortString = (String) msg.obj;
-                ComponentName transportComponent =
-                        ComponentName.unflattenFromString(componentShortString);
-                synchronized (mTransportLock) {
-                    if (mBoundTransports.containsValue(transportComponent)) {
-                        Slog.d(TAG, "Explicit rebinding timeout passed, but already bound to "
-                                + componentShortString + " so not attempting to rebind");
-                        return;
-                    }
-                    Slog.d(TAG, "Explicit rebinding timeout passed, attempting rebinding to: "
-                            + componentShortString);
-                    // unbind the existing (broken) connection
-                    TransportConnection conn = mValidTransports.get(transportComponent);
-                    if (conn != null) {
-                        mContext.unbindService(conn);
-                        Slog.d(TAG, "Unbinding the existing (broken) connection to transport: "
-                                + componentShortString);
-                    }
-                }
-                // rebind to transport
-                tryBindTransport(transportComponent);
-            } else {
-                Slog.e(TAG, "Unknown message sent to RebindOnTimeoutHandler, msg.what: "
-                        + msg.what);
-            }
-        }
-    }
-
-    private static void log_verbose(String message) {
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Slog.v(TAG, message);
-        }
-    }
-
-    // These only exists to make it testable with Robolectric, which is not updated to API level 24
-    // yet.
-    // TODO: Get rid of this once Robolectric is updated.
-    private static ComponentName getComponentName(ServiceInfo serviceInfo) {
-        return new ComponentName(serviceInfo.packageName, serviceInfo.name);
-    }
-
-    // These only exists to make it testable with Robolectric, which is not updated to API level 24
-    // yet.
-    // TODO: Get rid of this once Robolectric is updated.
-    public static UserHandle createSystemUserHandle() {
-        return new UserHandle(UserHandle.USER_SYSTEM);
+    private static Predicate<ComponentName> fromPackageFilter(String packageName) {
+        return transportComponent -> packageName.equals(transportComponent.getPackageName());
     }
 
     private static class TransportDescription {
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index c232241..cc3af8c 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -907,7 +907,15 @@
                         backupData = ParcelFileDescriptor.open(mBackupDataName,
                                 ParcelFileDescriptor.MODE_READ_ONLY);
                         backupManagerService.addBackupTrace("sending data to transport");
-                        int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
+
+                        int userInitiatedFlag =
+                                mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
+                        int incrementalFlag =
+                                mSavedStateName.length() == 0
+                                    ? BackupTransport.FLAG_NON_INCREMENTAL
+                                    : BackupTransport.FLAG_INCREMENTAL;
+                        int flags = userInitiatedFlag | incrementalFlag;
+
                         mStatus = transport.performBackup(mCurrentPackage, backupData, flags);
                     }
 
diff --git a/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
new file mode 100644
index 0000000..391ec2d
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
@@ -0,0 +1,33 @@
+/*
+ * 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
+ */
+
+package com.android.server.backup.transport;
+
+import com.android.server.backup.TransportManager;
+
+/**
+ * Listener called when a transport is registered with the {@link TransportManager}. Can be set
+ * using {@link TransportManager#setOnTransportRegisteredListener(OnTransportRegisteredListener)}.
+ */
+@FunctionalInterface
+public interface OnTransportRegisteredListener {
+    /**
+     * Called when a transport is successfully registered.
+     * @param transportName The name of the transport.
+     * @param transportDirName The dir name of the transport.
+     */
+    public void onTransportRegistered(String transportName, String transportDirName);
+}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java
index 7bd9111..bd4a0bb 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java
@@ -236,7 +236,7 @@
                                     mBindIntent,
                                     mConnection,
                                     Context.BIND_AUTO_CREATE,
-                                    TransportManager.createSystemUserHandle());
+                                    UserHandle.SYSTEM);
                     if (hasBound) {
                         // We don't need to set a time-out because we are guaranteed to get a call
                         // back in ServiceConnection, either an onServiceConnected() or
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 86063c3..15c0f3c 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -89,10 +89,10 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.LocalLog;
 import com.android.server.ForceAppStandbyTracker.Listener;
-import com.android.server.LocalServices;
 
 /**
  * Alarm manager implementaion.
@@ -152,6 +152,8 @@
     private long mLastTickSet;
     private long mLastTickIssued; // elapsed
     private long mLastTickReceived;
+    private long mLastTickAdded;
+    private long mLastTickRemoved;
     int mBroadcastRefCount = 0;
     PowerManager.WakeLock mWakeLock;
     boolean mLastWakeLockUnimportantForLogging;
@@ -431,6 +433,9 @@
             end = seed.maxWhenElapsed;
             flags = seed.flags;
             alarms.add(seed);
+            if (seed.operation == mTimeTickSender) {
+                mLastTickAdded = System.currentTimeMillis();
+            }
         }
 
         int size() {
@@ -453,6 +458,9 @@
                 index = 0 - index - 1;
             }
             alarms.add(index, alarm);
+            if (alarm.operation == mTimeTickSender) {
+                mLastTickAdded = System.currentTimeMillis();
+            }
             if (DEBUG_BATCH) {
                 Slog.v(TAG, "Adding " + alarm + " to " + this);
             }
@@ -484,6 +492,9 @@
                     if (alarm.alarmClock != null) {
                         mNextAlarmClockMayChange = true;
                     }
+                    if (alarm.operation == mTimeTickSender) {
+                        mLastTickRemoved = System.currentTimeMillis();
+                    }
                 } else {
                     if (alarm.whenElapsed > newStart) {
                         newStart = alarm.whenElapsed;
@@ -700,6 +711,39 @@
         }
         return -1;
     }
+    /** @return total count of the alarms in a set of alarm batches. */
+    static int getAlarmCount(ArrayList<Batch> batches) {
+        int ret = 0;
+
+        final int size = batches.size();
+        for (int i = 0; i < size; i++) {
+            ret += batches.get(i).size();
+        }
+        return ret;
+    }
+
+    boolean haveAlarmsTimeTickAlarm(ArrayList<Alarm> alarms) {
+        if (alarms.size() == 0) {
+            return false;
+        }
+        final int batchSize = alarms.size();
+        for (int j = 0; j < batchSize; j++) {
+            if (alarms.get(j).operation == mTimeTickSender) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean haveBatchesTimeTickAlarm(ArrayList<Batch> batches) {
+        final int numBatches = batches.size();
+        for (int i = 0; i < numBatches; i++) {
+            if (haveAlarmsTimeTickAlarm(batches.get(i).alarms)) {
+                return true;
+            }
+        }
+        return false;
+    }
 
     // The RTC clock has moved arbitrarily, so we need to recalculate all the batching
     void rebatchAllAlarms() {
@@ -709,6 +753,11 @@
     }
 
     void rebatchAllAlarmsLocked(boolean doValidate) {
+        final int oldCount =
+                getAlarmCount(mAlarmBatches) + ArrayUtils.size(mPendingWhileIdleAlarms);
+        final boolean oldHasTick = haveBatchesTimeTickAlarm(mAlarmBatches)
+                || haveAlarmsTimeTickAlarm(mPendingWhileIdleAlarms);
+
         ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone();
         mAlarmBatches.clear();
         Alarm oldPendingIdleUntil = mPendingIdleUntil;
@@ -729,6 +778,18 @@
                 restorePendingWhileIdleAlarmsLocked();
             }
         }
+        final int newCount =
+                getAlarmCount(mAlarmBatches) + ArrayUtils.size(mPendingWhileIdleAlarms);
+        final boolean newHasTick = haveBatchesTimeTickAlarm(mAlarmBatches)
+                || haveAlarmsTimeTickAlarm(mPendingWhileIdleAlarms);
+
+        if (oldCount != newCount) {
+            Slog.wtf(TAG, "Rebatching: total count changed from " + oldCount + " to " + newCount);
+        }
+        if (oldHasTick != newHasTick) {
+            Slog.wtf(TAG, "Rebatching: hasTick changed from " + oldHasTick + " to " + newHasTick);
+        }
+
         rescheduleKernelAlarmsLocked();
         updateNextAlarmClockLocked();
     }
@@ -1603,7 +1664,7 @@
 
             final long nowRTC = System.currentTimeMillis();
             final long nowELAPSED = SystemClock.elapsedRealtime();
-            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
 
             pw.print("  nowRTC="); pw.print(nowRTC);
             pw.print("="); pw.print(sdf.format(new Date(nowRTC)));
@@ -1613,10 +1674,11 @@
             pw.print("="); pw.println(sdf.format(new Date(mLastTimeChangeClockTime)));
             pw.print("  mLastTimeChangeRealtime="); pw.println(mLastTimeChangeRealtime);
             pw.print("  mLastTickIssued=");
-            TimeUtils.formatDuration(mLastTickIssued - nowELAPSED, pw);
-            pw.println();
+            pw.println(sdf.format(new Date(nowRTC - (nowELAPSED - mLastTickIssued))));
             pw.print("  mLastTickReceived="); pw.println(sdf.format(new Date(mLastTickReceived)));
             pw.print("  mLastTickSet="); pw.println(sdf.format(new Date(mLastTickSet)));
+            pw.print("  mLastTickAdded="); pw.println(sdf.format(new Date(mLastTickAdded)));
+            pw.print("  mLastTickRemoved="); pw.println(sdf.format(new Date(mLastTickRemoved)));
             pw.println();
             if (!mInteractive) {
                 pw.print("  Time since non-interactive: ");
@@ -2400,6 +2462,10 @@
     }
 
     void removeLocked(final int uid) {
+        if (uid == Process.SYSTEM_UID) {
+            Slog.wtf(TAG, "removeLocked: Shouldn't for UID=" + uid);
+            return;
+        }
         boolean didRemove = false;
         final Predicate<Alarm> whichAlarms = (Alarm a) -> a.uid == uid;
         for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
@@ -2448,6 +2514,7 @@
 
         boolean didRemove = false;
         final Predicate<Alarm> whichAlarms = (Alarm a) -> a.matches(packageName);
+        final boolean oldHasTick = haveBatchesTimeTickAlarm(mAlarmBatches);
         for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
             Batch b = mAlarmBatches.get(i);
             didRemove |= b.remove(whichAlarms);
@@ -2455,6 +2522,11 @@
                 mAlarmBatches.remove(i);
             }
         }
+        final boolean newHasTick = haveBatchesTimeTickAlarm(mAlarmBatches);
+        if (oldHasTick != newHasTick) {
+            Slog.wtf(TAG, "removeLocked: hasTick changed from " + oldHasTick + " to " + newHasTick);
+        }
+
         for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
             final Alarm a = mPendingWhileIdleAlarms.get(i);
             if (a.matches(packageName)) {
@@ -2484,6 +2556,10 @@
     }
 
     void removeForStoppedLocked(final int uid) {
+        if (uid == Process.SYSTEM_UID) {
+            Slog.wtf(TAG, "removeForStoppedLocked: Shouldn't for UID=" + uid);
+            return;
+        }
         boolean didRemove = false;
         final Predicate<Alarm> whichAlarms = (Alarm a) -> {
             try {
@@ -2524,6 +2600,10 @@
     }
 
     void removeUserLocked(int userHandle) {
+        if (userHandle == UserHandle.USER_SYSTEM) {
+            Slog.wtf(TAG, "removeForStoppedLocked: Shouldn't for user=" + userHandle);
+            return;
+        }
         boolean didRemove = false;
         final Predicate<Alarm> whichAlarms =
                 (Alarm a) -> UserHandle.getUserId(a.creatorUid) == userHandle;
@@ -3688,6 +3768,9 @@
                                     mDeliveryTracker, mHandler, null,
                                     allowWhileIdle ? mIdleOptions : null);
                 } catch (PendingIntent.CanceledException e) {
+                    if (alarm.operation == mTimeTickSender) {
+                        Slog.wtf(TAG, "mTimeTickSender canceled");
+                    }
                     if (alarm.repeatInterval > 0) {
                         // This IntentSender is no longer valid, but this
                         // is a repeating alarm, so toss it
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index dcb0fab..af0b66d 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -298,6 +298,8 @@
         final int lowPowerModeTriggerLevel = Settings.Global.getInt(resolver,
                 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, defWarnLevel);
 
+        // NOTE: Keep the logic in sync with PowerUI.java in systemUI.
+        // TODO: Propagate this value from BatteryService to system UI, really.
         mLowBatteryWarningLevel = Math.min(defWarnLevel, lowPowerModeTriggerLevel);
 
         if (mLowBatteryWarningLevel == 0) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 63ee9fa..77521df 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -226,7 +226,11 @@
     @GuardedBy("mVpns")
     private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>();
 
+    // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
+    // a direct call to LockdownVpnTracker.isEnabled().
+    @GuardedBy("mVpns")
     private boolean mLockdownEnabled;
+    @GuardedBy("mVpns")
     private LockdownVpnTracker mLockdownTracker;
 
     final private Context mContext;
@@ -997,9 +1001,9 @@
     }
 
     private Network[] getVpnUnderlyingNetworks(int uid) {
-        if (!mLockdownEnabled) {
-            int user = UserHandle.getUserId(uid);
-            synchronized (mVpns) {
+        synchronized (mVpns) {
+            if (!mLockdownEnabled) {
+                int user = UserHandle.getUserId(uid);
                 Vpn vpn = mVpns.get(user);
                 if (vpn != null && vpn.appliesToUid(uid)) {
                     return vpn.getUnderlyingNetworks();
@@ -1087,8 +1091,10 @@
         if (isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, ignoreBlocked)) {
             state.networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
         }
-        if (mLockdownTracker != null) {
-            mLockdownTracker.augmentNetworkInfo(state.networkInfo);
+        synchronized (mVpns) {
+            if (mLockdownTracker != null) {
+                mLockdownTracker.augmentNetworkInfo(state.networkInfo);
+            }
         }
     }
 
@@ -1253,8 +1259,8 @@
             result.put(nai.network, nc);
         }
 
-        if (!mLockdownEnabled) {
-            synchronized (mVpns) {
+        synchronized (mVpns) {
+            if (!mLockdownEnabled) {
                 Vpn vpn = mVpns.get(userId);
                 if (vpn != null) {
                     Network[] networks = vpn.getUnderlyingNetworks();
@@ -1580,9 +1586,11 @@
     }
 
     private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
-        if (mLockdownTracker != null) {
-            info = new NetworkInfo(info);
-            mLockdownTracker.augmentNetworkInfo(info);
+        synchronized (mVpns) {
+            if (mLockdownTracker != null) {
+                info = new NetworkInfo(info);
+                mLockdownTracker.augmentNetworkInfo(info);
+            }
         }
 
         Intent intent = new Intent(bcastType);
@@ -2500,6 +2508,7 @@
     private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) {
         nri.unlinkDeathRecipient();
         mNetworkRequests.remove(nri.request);
+
         synchronized (mUidToNetworkRequestCount) {
             int requests = mUidToNetworkRequestCount.get(nri.mUid, 0);
             if (requests < 1) {
@@ -2512,6 +2521,7 @@
                 mUidToNetworkRequestCount.put(nri.mUid, requests - 1);
             }
         }
+
         mNetworkRequestInfoLogs.log("RELEASE " + nri);
         if (nri.request.isRequest()) {
             boolean wasKept = false;
@@ -3434,9 +3444,9 @@
     public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
             int userId) {
         enforceCrossUserPermission(userId);
-        throwIfLockdownEnabled();
 
         synchronized (mVpns) {
+            throwIfLockdownEnabled();
             Vpn vpn = mVpns.get(userId);
             if (vpn != null) {
                 return vpn.prepare(oldPackage, newPackage);
@@ -3480,9 +3490,9 @@
      */
     @Override
     public ParcelFileDescriptor establishVpn(VpnConfig config) {
-        throwIfLockdownEnabled();
         int user = UserHandle.getUserId(Binder.getCallingUid());
         synchronized (mVpns) {
+            throwIfLockdownEnabled();
             return mVpns.get(user).establish(config);
         }
     }
@@ -3493,13 +3503,13 @@
      */
     @Override
     public void startLegacyVpn(VpnProfile profile) {
-        throwIfLockdownEnabled();
+        int user = UserHandle.getUserId(Binder.getCallingUid());
         final LinkProperties egress = getActiveLinkProperties();
         if (egress == null) {
             throw new IllegalStateException("Missing active network connection");
         }
-        int user = UserHandle.getUserId(Binder.getCallingUid());
         synchronized (mVpns) {
+            throwIfLockdownEnabled();
             mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
         }
     }
@@ -3525,11 +3535,11 @@
     @Override
     public VpnInfo[] getAllVpnInfo() {
         enforceConnectivityInternalPermission();
-        if (mLockdownEnabled) {
-            return new VpnInfo[0];
-        }
-
         synchronized (mVpns) {
+            if (mLockdownEnabled) {
+                return new VpnInfo[0];
+            }
+
             List<VpnInfo> infoList = new ArrayList<>();
             for (int i = 0; i < mVpns.size(); i++) {
                 VpnInfo info = createVpnInfo(mVpns.valueAt(i));
@@ -3594,33 +3604,33 @@
             return false;
         }
 
-        // Tear down existing lockdown if profile was removed
-        mLockdownEnabled = LockdownVpnTracker.isEnabled();
-        if (mLockdownEnabled) {
-            byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
-            if (profileTag == null) {
-                Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore");
-                return false;
-            }
-            String profileName = new String(profileTag);
-            final VpnProfile profile = VpnProfile.decode(
-                    profileName, mKeyStore.get(Credentials.VPN + profileName));
-            if (profile == null) {
-                Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName);
-                setLockdownTracker(null);
-                return true;
-            }
-            int user = UserHandle.getUserId(Binder.getCallingUid());
-            synchronized (mVpns) {
+        synchronized (mVpns) {
+            // Tear down existing lockdown if profile was removed
+            mLockdownEnabled = LockdownVpnTracker.isEnabled();
+            if (mLockdownEnabled) {
+                byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
+                if (profileTag == null) {
+                    Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore");
+                    return false;
+                }
+                String profileName = new String(profileTag);
+                final VpnProfile profile = VpnProfile.decode(
+                        profileName, mKeyStore.get(Credentials.VPN + profileName));
+                if (profile == null) {
+                    Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName);
+                    setLockdownTracker(null);
+                    return true;
+                }
+                int user = UserHandle.getUserId(Binder.getCallingUid());
                 Vpn vpn = mVpns.get(user);
                 if (vpn == null) {
                     Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
                     return false;
                 }
                 setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, vpn, profile));
+            } else {
+                setLockdownTracker(null);
             }
-        } else {
-            setLockdownTracker(null);
         }
 
         return true;
@@ -3630,6 +3640,7 @@
      * Internally set new {@link LockdownVpnTracker}, shutting down any existing
      * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
      */
+    @GuardedBy("mVpns")
     private void setLockdownTracker(LockdownVpnTracker tracker) {
         // Shutdown any existing tracker
         final LockdownVpnTracker existing = mLockdownTracker;
@@ -3644,6 +3655,7 @@
         }
     }
 
+    @GuardedBy("mVpns")
     private void throwIfLockdownEnabled() {
         if (mLockdownEnabled) {
             throw new IllegalStateException("Unavailable in lockdown mode");
@@ -3691,12 +3703,12 @@
         enforceConnectivityInternalPermission();
         enforceCrossUserPermission(userId);
 
-        // Can't set always-on VPN if legacy VPN is already in lockdown mode.
-        if (LockdownVpnTracker.isEnabled()) {
-            return false;
-        }
-
         synchronized (mVpns) {
+            // Can't set always-on VPN if legacy VPN is already in lockdown mode.
+            if (LockdownVpnTracker.isEnabled()) {
+                return false;
+            }
+
             Vpn vpn = mVpns.get(userId);
             if (vpn == null) {
                 Slog.w(TAG, "User " + userId + " has no Vpn configuration");
@@ -3872,9 +3884,9 @@
             }
             userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId);
             mVpns.put(userId, userVpn);
-        }
-        if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
-            updateLockdownVpn();
+            if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
+                updateLockdownVpn();
+            }
         }
     }
 
@@ -3911,11 +3923,13 @@
     }
 
     private void onUserUnlocked(int userId) {
-        // User present may be sent because of an unlock, which might mean an unlocked keystore.
-        if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
-            updateLockdownVpn();
-        } else {
-            startAlwaysOnVpn(userId);
+        synchronized (mVpns) {
+            // User present may be sent because of an unlock, which might mean an unlocked keystore.
+            if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
+                updateLockdownVpn();
+            } else {
+                startAlwaysOnVpn(userId);
+            }
         }
     }
 
@@ -4595,51 +4609,67 @@
     }
 
     /**
-     * Update the NetworkCapabilities for {@code networkAgent} to {@code networkCapabilities}
-     * augmented with any stateful capabilities implied from {@code networkAgent}
-     * (e.g., validated status and captive portal status).
-     *
-     * @param oldScore score of the network before any of the changes that prompted us
-     *                 to call this function.
-     * @param nai the network having its capabilities updated.
-     * @param networkCapabilities the new network capabilities.
+     * Augments the NetworkCapabilities passed in by a NetworkAgent with capabilities that are
+     * maintained here that the NetworkAgent is not aware of (e.g., validated, captive portal,
+     * and foreground status).
      */
-    private void updateCapabilities(
-            int oldScore, NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) {
+    private NetworkCapabilities mixInCapabilities(NetworkAgentInfo nai, NetworkCapabilities nc) {
         // Once a NetworkAgent is connected, complain if some immutable capabilities are removed.
-        if (nai.everConnected && !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(
-                networkCapabilities)) {
-            // TODO: consider not complaining when a network agent degrade its capabilities if this
+        if (nai.everConnected &&
+                !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(nc)) {
+            // TODO: consider not complaining when a network agent degrades its capabilities if this
             // does not cause any request (that is not a listen) currently matching that agent to
             // stop being matched by the updated agent.
-            String diff = nai.networkCapabilities.describeImmutableDifferences(networkCapabilities);
+            String diff = nai.networkCapabilities.describeImmutableDifferences(nc);
             if (!TextUtils.isEmpty(diff)) {
                 Slog.wtf(TAG, "BUG: " + nai + " lost immutable capabilities:" + diff);
             }
         }
 
         // Don't modify caller's NetworkCapabilities.
-        networkCapabilities = new NetworkCapabilities(networkCapabilities);
+        NetworkCapabilities newNc = new NetworkCapabilities(nc);
         if (nai.lastValidated) {
-            networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
+            newNc.addCapability(NET_CAPABILITY_VALIDATED);
         } else {
-            networkCapabilities.removeCapability(NET_CAPABILITY_VALIDATED);
+            newNc.removeCapability(NET_CAPABILITY_VALIDATED);
         }
         if (nai.lastCaptivePortalDetected) {
-            networkCapabilities.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
+            newNc.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
         } else {
-            networkCapabilities.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
+            newNc.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
         }
         if (nai.isBackgroundNetwork()) {
-            networkCapabilities.removeCapability(NET_CAPABILITY_FOREGROUND);
+            newNc.removeCapability(NET_CAPABILITY_FOREGROUND);
         } else {
-            networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND);
+            newNc.addCapability(NET_CAPABILITY_FOREGROUND);
         }
 
-        if (Objects.equals(nai.networkCapabilities, networkCapabilities)) return;
+        return newNc;
+    }
+
+    /**
+     * Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically:
+     *
+     * 1. Calls mixInCapabilities to merge the passed-in NetworkCapabilities {@code nc} with the
+     *    capabilities we manage and store in {@code nai}, such as validated status and captive
+     *    portal status)
+     * 2. Takes action on the result: changes network permissions, sends CAP_CHANGED callbacks, and
+     *    potentially triggers rematches.
+     * 3. Directly informs other network stack components (NetworkStatsService, VPNs, etc. of the
+     *    change.)
+     *
+     * @param oldScore score of the network before any of the changes that prompted us
+     *                 to call this function.
+     * @param nai the network having its capabilities updated.
+     * @param nc the new network capabilities.
+     */
+    private void updateCapabilities(int oldScore, NetworkAgentInfo nai, NetworkCapabilities nc) {
+        NetworkCapabilities newNc = mixInCapabilities(nai, nc);
+
+        if (Objects.equals(nai.networkCapabilities, newNc)) return;
 
         final String oldPermission = getNetworkPermission(nai.networkCapabilities);
-        final String newPermission = getNetworkPermission(networkCapabilities);
+        final String newPermission = getNetworkPermission(newNc);
         if (!Objects.equals(oldPermission, newPermission) && nai.created && !nai.isVPN()) {
             try {
                 mNetd.setNetworkPermission(nai.network.netId, newPermission);
@@ -4651,11 +4681,10 @@
         final NetworkCapabilities prevNc;
         synchronized (nai) {
             prevNc = nai.networkCapabilities;
-            nai.networkCapabilities = networkCapabilities;
+            nai.networkCapabilities = newNc;
         }
 
-        if (nai.getCurrentScore() == oldScore &&
-                networkCapabilities.equalRequestableCapabilities(prevNc)) {
+        if (nai.getCurrentScore() == oldScore && newNc.equalRequestableCapabilities(prevNc)) {
             // If the requestable capabilities haven't changed, and the score hasn't changed, then
             // the change we're processing can't affect any requests, it can only affect the listens
             // on this network. We might have been called by rematchNetworkAndRequests when a
@@ -4671,15 +4700,15 @@
         // Report changes that are interesting for network statistics tracking.
         if (prevNc != null) {
             final boolean meteredChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_METERED) !=
-                    networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED);
+                    newNc.hasCapability(NET_CAPABILITY_NOT_METERED);
             final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) !=
-                    networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+                    newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
             if (meteredChanged || roamingChanged) {
                 notifyIfacesChangedForNetworkStats();
             }
         }
 
-        if (!networkCapabilities.hasTransport(TRANSPORT_VPN)) {
+        if (!newNc.hasTransport(TRANSPORT_VPN)) {
             // Tell VPNs about updated capabilities, since they may need to
             // bubble those changes through.
             synchronized (mVpns) {
@@ -5203,11 +5232,13 @@
     }
 
     private void notifyLockdownVpn(NetworkAgentInfo nai) {
-        if (mLockdownTracker != null) {
-            if (nai != null && nai.isVPN()) {
-                mLockdownTracker.onVpnStateChanged(nai.networkInfo);
-            } else {
-                mLockdownTracker.onNetworkInfoChanged();
+        synchronized (mVpns) {
+            if (mLockdownTracker != null) {
+                if (nai != null && nai.isVPN()) {
+                    mLockdownTracker.onVpnStateChanged(nai.networkInfo);
+                } else {
+                    mLockdownTracker.onNetworkInfoChanged();
+                }
             }
         }
     }
@@ -5437,28 +5468,28 @@
 
     @Override
     public boolean addVpnAddress(String address, int prefixLength) {
-        throwIfLockdownEnabled();
         int user = UserHandle.getUserId(Binder.getCallingUid());
         synchronized (mVpns) {
+            throwIfLockdownEnabled();
             return mVpns.get(user).addAddress(address, prefixLength);
         }
     }
 
     @Override
     public boolean removeVpnAddress(String address, int prefixLength) {
-        throwIfLockdownEnabled();
         int user = UserHandle.getUserId(Binder.getCallingUid());
         synchronized (mVpns) {
+            throwIfLockdownEnabled();
             return mVpns.get(user).removeAddress(address, prefixLength);
         }
     }
 
     @Override
     public boolean setUnderlyingNetworksForVpn(Network[] networks) {
-        throwIfLockdownEnabled();
         int user = UserHandle.getUserId(Binder.getCallingUid());
-        boolean success;
+        final boolean success;
         synchronized (mVpns) {
+            throwIfLockdownEnabled();
             success = mVpns.get(user).setUnderlyingNetworks(networks);
         }
         if (success) {
@@ -5518,31 +5549,31 @@
                     setAlwaysOnVpnPackage(userId, null, false);
                     setVpnPackageAuthorization(alwaysOnPackage, userId, false);
                 }
-            }
 
-            // Turn Always-on VPN off
-            if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    mKeyStore.delete(Credentials.LOCKDOWN_VPN);
-                    mLockdownEnabled = false;
-                    setLockdownTracker(null);
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
+                // Turn Always-on VPN off
+                if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        mKeyStore.delete(Credentials.LOCKDOWN_VPN);
+                        mLockdownEnabled = false;
+                        setLockdownTracker(null);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                    }
                 }
-            }
 
-            // Turn VPN off
-            VpnConfig vpnConfig = getVpnConfig(userId);
-            if (vpnConfig != null) {
-                if (vpnConfig.legacy) {
-                    prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
-                } else {
-                    // Prevent this app (packagename = vpnConfig.user) from initiating VPN connections
-                    // in the future without user intervention.
-                    setVpnPackageAuthorization(vpnConfig.user, userId, false);
+                // Turn VPN off
+                VpnConfig vpnConfig = getVpnConfig(userId);
+                if (vpnConfig != null) {
+                    if (vpnConfig.legacy) {
+                        prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
+                    } else {
+                        // Prevent this app (packagename = vpnConfig.user) from initiating
+                        // VPN connections in the future without user intervention.
+                        setVpnPackageAuthorization(vpnConfig.user, userId, false);
 
-                    prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
+                        prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 40e6d26..8f646e7 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -2508,12 +2508,16 @@
 
     @Override
     public void removeNetwork(int netId) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
 
         try {
-            mConnector.execute("network", "destroy", netId);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+            mNetdService.networkDestroy(netId);
+        } catch (ServiceSpecificException e) {
+            Log.w(TAG, "removeNetwork(" + netId + "): ", e);
+            throw e;
+        } catch (RemoteException e) {
+            Log.w(TAG, "removeNetwork(" + netId + "): ", e);
+            throw e.rethrowAsRuntimeException();
         }
     }
 
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index dc2f2a5..7361e70 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -49,6 +49,7 @@
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.content.res.ObbInfo;
+import android.database.ContentObserver;
 import android.net.TrafficStats;
 import android.net.Uri;
 import android.os.Binder;
@@ -170,6 +171,10 @@
     // Static direct instance pointer for the tightly-coupled idle service to use
     static StorageManagerService sSelf = null;
 
+    /* Read during boot to decide whether to enable zram when available */
+    private static final String ZRAM_ENABLED_PROPERTY =
+        "persist.sys.zram_enabled";
+
     public static class Lifecycle extends SystemService {
         private StorageManagerService mStorageManagerService;
 
@@ -733,6 +738,41 @@
 
         // Start scheduling nominally-daily fstrim operations
         MountServiceIdler.scheduleIdlePass(mContext);
+
+        // Toggle zram-enable system property in response to settings
+        mContext.getContentResolver().registerContentObserver(
+            Settings.Global.getUriFor(Settings.Global.ZRAM_ENABLED),
+            false /*notifyForDescendants*/,
+            new ContentObserver(null /* current thread */) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    refreshZramSettings();
+                }
+            });
+        refreshZramSettings();
+    }
+
+    /**
+     * Update the zram_enabled system property (which init reads to
+     * decide whether to enable zram) to reflect the zram_enabled
+     * preference (which we can change for experimentation purposes).
+     */
+    private void refreshZramSettings() {
+        String propertyValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY);
+        if ("".equals(propertyValue)) {
+            return;  // System doesn't have zram toggling support
+        }
+        String desiredPropertyValue =
+            Settings.Global.getInt(mContext.getContentResolver(),
+                                   Settings.Global.ZRAM_ENABLED,
+                                   1) != 0
+            ? "1" : "0";
+        if (!desiredPropertyValue.equals(propertyValue)) {
+            // Avoid redundant disk writes by setting only if we're
+            // changing the property value. There's no race: we're the
+            // sole writer.
+            SystemProperties.set(ZRAM_ENABLED_PROPERTY, desiredPropertyValue);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 831c9cb..6747be3 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -147,6 +147,8 @@
 
     private int[] mDataActivationState;
 
+    private boolean[] mUserMobileDataState;
+
     private SignalStrength[] mSignalStrength;
 
     private boolean[] mMessageWaiting;
@@ -304,6 +306,7 @@
         mServiceState = new ServiceState[numPhones];
         mVoiceActivationState = new int[numPhones];
         mDataActivationState = new int[numPhones];
+        mUserMobileDataState = new boolean[numPhones];
         mSignalStrength = new SignalStrength[numPhones];
         mMessageWaiting = new boolean[numPhones];
         mCallForwarding = new boolean[numPhones];
@@ -320,6 +323,7 @@
             mCallIncomingNumber[i] =  "";
             mServiceState[i] =  new ServiceState();
             mSignalStrength[i] =  new SignalStrength();
+            mUserMobileDataState[i] = false;
             mMessageWaiting[i] =  false;
             mCallForwarding[i] =  false;
             mCellLocation[i] = new Bundle();
@@ -656,6 +660,13 @@
                             remove(r.binder);
                         }
                     }
+                    if ((events & PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) != 0) {
+                        try {
+                            r.callback.onUserMobileDataStateChanged(mUserMobileDataState[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
                 }
             }
         } else {
@@ -1012,6 +1023,33 @@
         }
     }
 
+    public void notifyUserMobileDataStateChangedForPhoneId(int phoneId, int subId, boolean state) {
+        if (!checkNotifyPermission("notifyUserMobileDataStateChanged()")) {
+            return;
+        }
+        if (VDBG) {
+            log("notifyUserMobileDataStateChangedForSubscriberPhoneID: subId=" + phoneId
+                    + " state=" + state);
+        }
+        synchronized (mRecords) {
+            if (validatePhoneId(phoneId)) {
+                mMessageWaiting[phoneId] = state;
+                for (Record r : mRecords) {
+                    if (r.matchPhoneStateListenerEvent(
+                            PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) &&
+                            idMatch(r.subId, subId, phoneId)) {
+                        try {
+                            r.callback.onUserMobileDataStateChanged(state);
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     public void notifyCallForwardingChanged(boolean cfi) {
         notifyCallForwardingChangedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cfi);
     }
@@ -1374,6 +1412,7 @@
                 pw.println("mServiceState=" + mServiceState[i]);
                 pw.println("mVoiceActivationState= " + mVoiceActivationState[i]);
                 pw.println("mDataActivationState= " + mDataActivationState[i]);
+                pw.println("mUserMobileDataState= " + mUserMobileDataState[i]);
                 pw.println("mSignalStrength=" + mSignalStrength[i]);
                 pw.println("mMessageWaiting=" + mMessageWaiting[i]);
                 pw.println("mCallForwarding=" + mCallForwarding[i]);
@@ -1755,6 +1794,18 @@
             }
         }
 
+        if ((events & PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) != 0) {
+            try {
+                if (VDBG) {
+                    log("checkPossibleMissNotify: onUserMobileDataStateChanged phoneId="
+                            + phoneId + " umds=" + mUserMobileDataState[phoneId]);
+                }
+                r.callback.onUserMobileDataStateChanged(mUserMobileDataState[phoneId]);
+            } catch (RemoteException ex) {
+                mRemoveList.add(r.binder);
+            }
+        }
+
         if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
             try {
                 if (VDBG) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6565187..c9b9a40 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8401,7 +8401,7 @@
                             public void onDismissCancelled() throws RemoteException {
                                 // Do nothing
                             }
-                        });
+                        }, null /* message */);
                     } catch (RemoteException e) {
                         // Local call
                     }
@@ -25130,6 +25130,11 @@
         public void registerScreenObserver(ScreenObserver observer) {
             mScreenObservers.add(observer);
         }
+
+        @Override
+        public boolean canStartMoreUsers() {
+            return mUserController.canStartMoreUsers();
+        }
     }
 
     /**
@@ -25277,11 +25282,15 @@
     }
 
     @Override
-    public void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback)
-            throws RemoteException {
+    public void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback,
+            CharSequence message) throws RemoteException {
+        if (message != null) {
+            enforceCallingPermission(permission.SHOW_KEYGUARD_MESSAGE,
+                    "dismissKeyguard()");
+        }
         final long callingId = Binder.clearCallingIdentity();
         try {
-            mKeyguardController.dismissKeyguard(token, callback);
+            mKeyguardController.dismissKeyguard(token, callback, message);
         } finally {
             Binder.restoreCallingIdentity(callingId);
         }
@@ -25336,6 +25345,10 @@
                 }
             }
         }
+        if (updateFrameworkRes && mWindowManager != null) {
+            ActivityThread.currentActivityThread().getExecutor().execute(
+                    mWindowManager::onOverlayChanged);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ActivityStartInterceptor.java b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
index 6684f25..0480646 100644
--- a/services/core/java/com/android/server/am/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
@@ -30,6 +30,7 @@
 import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
 
 import android.app.ActivityOptions;
+import android.app.AppGlobals;
 import android.app.KeyguardManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.Context;
@@ -40,10 +41,12 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.os.Binder;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.HarmfulAppWarningActivity;
 import com.android.internal.app.UnlaunchableAppActivity;
 import com.android.server.LocalServices;
 
@@ -115,6 +118,15 @@
         mCallingPackage = callingPackage;
     }
 
+    private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) {
+        final IIntentSender target = mService.getIntentSenderLocked(
+                INTENT_SENDER_ACTIVITY, mCallingPackage, callingUid, mUserId, null /*token*/,
+                null /*resultCode*/, 0 /*requestCode*/,
+                new Intent[] { mIntent }, new String[] { mResolvedType },
+                flags, null /*bOptions*/);
+        return new IntentSender(target);
+    }
+
     /**
      * Intercept the launch intent based on various signals. If an interception happened the
      * internal variables get assigned and need to be read explicitly by the caller.
@@ -144,6 +156,11 @@
             // be unlocked when profile's user is running.
             return true;
         }
+        if (interceptHarmfulAppIfNeeded()) {
+            // If the app has a "harmful app" warning associated with it, we should ask to uninstall
+            // before issuing the work challenge.
+            return true;
+        }
         return interceptWorkProfileChallengeIfNeeded();
     }
 
@@ -152,13 +169,10 @@
         if (!mUserManager.isQuietModeEnabled(UserHandle.of(mUserId))) {
             return false;
         }
-        IIntentSender target = mService.getIntentSenderLocked(
-                INTENT_SENDER_ACTIVITY, mCallingPackage, mCallingUid, mUserId, null, null, 0,
-                new Intent[] {mIntent}, new String[] {mResolvedType},
-                FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT, null);
+        IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
+                FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT);
 
-        mIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(mUserId,
-                new IntentSender(target));
+        mIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(mUserId, target);
         mCallingPid = mRealCallingPid;
         mCallingUid = mRealCallingUid;
         mResolvedType = null;
@@ -240,11 +254,8 @@
             return null;
         }
         // TODO(b/28935539): should allow certain activities to bypass work challenge
-        final IIntentSender target = mService.getIntentSenderLocked(
-                INTENT_SENDER_ACTIVITY, callingPackage,
-                Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent },
-                new String[]{ resolvedType },
-                FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE, null);
+        final IntentSender target = createIntentSenderForOriginalIntent(Binder.getCallingUid(),
+                FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE);
         final KeyguardManager km = (KeyguardManager) mServiceContext
                 .getSystemService(KEYGUARD_SERVICE);
         final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, userId);
@@ -254,8 +265,36 @@
         newIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |
                 FLAG_ACTIVITY_TASK_ON_HOME);
         newIntent.putExtra(EXTRA_PACKAGE_NAME, aInfo.packageName);
-        newIntent.putExtra(EXTRA_INTENT, new IntentSender(target));
+        newIntent.putExtra(EXTRA_INTENT, target);
         return newIntent;
     }
 
+    private boolean interceptHarmfulAppIfNeeded() {
+        CharSequence harmfulAppWarning;
+        try {
+            harmfulAppWarning = AppGlobals.getPackageManager().getHarmfulAppWarning(
+                    mAInfo.packageName, mUserId);
+        } catch (RemoteException e) {
+            return false;
+        }
+
+        if (harmfulAppWarning == null) {
+            return false;
+        }
+
+        final IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
+                FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE);
+
+        mIntent = HarmfulAppWarningActivity.createHarmfulAppWarningIntent(mServiceContext,
+                mAInfo.packageName, target, harmfulAppWarning);
+
+        mCallingPid = mRealCallingPid;
+        mCallingUid = mRealCallingUid;
+        mResolvedType = null;
+
+        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId);
+        mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
+        return true;
+    }
+
 }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c9aa9a2..81e8eb0 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -933,21 +933,6 @@
         }
     }
 
-    public void noteWifiMulticastEnabledFromSource(WorkSource ws) {
-        enforceCallingPermission();
-        synchronized (mStats) {
-            mStats.noteWifiMulticastEnabledFromSourceLocked(ws);
-        }
-    }
-
-    @Override
-    public void noteWifiMulticastDisabledFromSource(WorkSource ws) {
-        enforceCallingPermission();
-        synchronized (mStats) {
-            mStats.noteWifiMulticastDisabledFromSourceLocked(ws);
-        }
-    }
-
     @Override
     public void noteNetworkInterfaceType(String iface, int networkType) {
         enforceCallingPermission();
diff --git a/services/core/java/com/android/server/am/ClientLifecycleManager.java b/services/core/java/com/android/server/am/ClientLifecycleManager.java
index 014f708..ae8d9fc 100644
--- a/services/core/java/com/android/server/am/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/am/ClientLifecycleManager.java
@@ -21,6 +21,7 @@
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
 import android.app.servertransaction.ActivityLifecycleItem;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 
@@ -42,9 +43,14 @@
      * @see ClientTransaction
      */
     void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+        final IApplicationThread client = transaction.getClient();
         transaction.schedule();
-        // TODO: b/70616950
-        //transaction.recycle();
+        if (!(client instanceof Binder)) {
+            // If client is not an instance of Binder - it's a remote call and at this point it is
+            // safe to recycle the object. All objects used for local calls will be recycled after
+            // the transaction is executed on client in ActivityThread.
+            transaction.recycle();
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
index 5632fc0..c9afc17 100644
--- a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
@@ -37,7 +37,8 @@
 
     private static final String[][] sGlobalSettingsMapping = new String[][] {
     //  List mapping entries in the following format:
-    //            {Settings.Global.SETTING_NAME, "system_property_name"},
+    //  {Settings.Global.SETTING_NAME, "system_property_name"},
+        {Settings.Global.SYS_VDSO, "sys.vdso"},
     };
 
 
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index 35f4f25..4d7bc1e 100644
--- a/services/core/java/com/android/server/am/KeyguardController.java
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -150,7 +150,7 @@
         }
     }
 
-    void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback) {
+    void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback, CharSequence message) {
         final ActivityRecord activityRecord = ActivityRecord.forTokenLocked(token);
         if (activityRecord == null || !activityRecord.visibleIgnoringKeyguard) {
             failCallback(callback);
@@ -164,7 +164,7 @@
             mStackSupervisor.wakeUp("dismissKeyguard");
         }
 
-        mWindowManager.dismissKeyguard(callback);
+        mWindowManager.dismissKeyguard(callback, message);
     }
 
     private void setKeyguardGoingAway(boolean keyguardGoingAway) {
@@ -304,7 +304,7 @@
         // insecure case, we actually show it on top of the lockscreen. See #canShowWhileOccluded.
         if (!mOccluded && mDismissingKeyguardActivity != null
                 && mWindowManager.isKeyguardSecure()) {
-            mWindowManager.dismissKeyguard(null /* callback */);
+            mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
             mDismissalRequested = true;
 
             // If we are about to unocclude the Keyguard, but we can dismiss it without security,
diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java
index ba3e25a..21f9135 100644
--- a/services/core/java/com/android/server/am/LockTaskController.java
+++ b/services/core/java/com/android/server/am/LockTaskController.java
@@ -752,7 +752,7 @@
                     USER_CURRENT) != 0;
             if (shouldLockKeyguard) {
                 mWindowManager.lockNow(null);
-                mWindowManager.dismissKeyguard(null /* callback */);
+                mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
                 getLockPatternUtils().requireCredentialEntry(USER_ALL);
             }
         } catch (Settings.SettingNotFoundException e) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c4fdffa..a327a01 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -101,6 +101,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -249,39 +250,51 @@
         }
     }
 
-    void stopRunningUsersLU(int maxRunningUsers) {
-        int currentlyRunning = mUserLru.size();
-        int i = 0;
-        while (currentlyRunning > maxRunningUsers && i < mUserLru.size()) {
-            Integer oldUserId = mUserLru.get(i);
-            UserState oldUss = mStartedUsers.get(oldUserId);
-            if (oldUss == null) {
+    List<Integer> getRunningUsersLU() {
+        ArrayList<Integer> runningUsers = new ArrayList<>();
+        for (Integer userId : mUserLru) {
+            UserState uss = mStartedUsers.get(userId);
+            if (uss == null) {
                 // Shouldn't happen, but be sane if it does.
-                mUserLru.remove(i);
-                currentlyRunning--;
                 continue;
             }
-            if (oldUss.state == UserState.STATE_STOPPING
-                    || oldUss.state == UserState.STATE_SHUTDOWN) {
+            if (uss.state == UserState.STATE_STOPPING
+                    || uss.state == UserState.STATE_SHUTDOWN) {
                 // This user is already stopping, doesn't count.
-                currentlyRunning--;
-                i++;
                 continue;
             }
-            if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId) {
-                // Owner/System user and current user can't be stopped. We count it as running
-                // when it is not a pure system user.
-                if (UserInfo.isSystemOnly(oldUserId)) {
-                    currentlyRunning--;
+            if (userId == UserHandle.USER_SYSTEM) {
+                // We only count system user as running when it is not a pure system user.
+                if (UserInfo.isSystemOnly(userId)) {
+                    continue;
                 }
-                i++;
+            }
+            runningUsers.add(userId);
+        }
+        return runningUsers;
+    }
+
+    void stopRunningUsersLU(int maxRunningUsers) {
+        List<Integer> currentlyRunning = getRunningUsersLU();
+        Iterator<Integer> iterator = currentlyRunning.iterator();
+        while (currentlyRunning.size() > maxRunningUsers && iterator.hasNext()) {
+            Integer userId = iterator.next();
+            if (userId == UserHandle.USER_SYSTEM || userId == mCurrentUserId) {
+                // Owner/System user and current user can't be stopped
                 continue;
             }
-            // This is a user to be stopped.
-            if (stopUsersLU(oldUserId, false, null) == USER_OP_SUCCESS) {
-                currentlyRunning--;
+            if (stopUsersLU(userId, false, null) == USER_OP_SUCCESS) {
+                iterator.remove();
             }
-            i++;
+        }
+    }
+
+    /**
+     * Returns if more users can be started without stopping currently running users.
+     */
+    boolean canStartMoreUsers() {
+        synchronized (mLock) {
+            return getRunningUsersLU().size() < mMaxRunningUsers;
         }
     }
 
@@ -768,34 +781,23 @@
     /**
      * Stops the guest or ephemeral user if it has gone to the background.
      */
-    private void stopGuestOrEphemeralUserIfBackground() {
-        IntArray userIds = new IntArray();
-        synchronized (mLock) {
-            final int num = mUserLru.size();
-            for (int i = 0; i < num; i++) {
-                Integer oldUserId = mUserLru.get(i);
-                UserState oldUss = mStartedUsers.get(oldUserId);
-                if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId
-                        || oldUss.state == UserState.STATE_STOPPING
-                        || oldUss.state == UserState.STATE_SHUTDOWN) {
-                    continue;
-                }
-                userIds.add(oldUserId);
-            }
+    private void stopGuestOrEphemeralUserIfBackground(int oldUserId) {
+        if (DEBUG_MU) Slog.i(TAG, "Stop guest or ephemeral user if background: " + oldUserId);
+        UserState oldUss = mStartedUsers.get(oldUserId);
+        if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId
+                || oldUss.state == UserState.STATE_STOPPING
+                || oldUss.state == UserState.STATE_SHUTDOWN) {
+            return;
         }
-        final int userIdsSize = userIds.size();
-        for (int i = 0; i < userIdsSize; i++) {
-            int oldUserId = userIds.get(i);
-            UserInfo userInfo = getUserInfo(oldUserId);
-            if (userInfo.isEphemeral()) {
-                LocalServices.getService(UserManagerInternal.class).onEphemeralUserStop(oldUserId);
-            }
-            if (userInfo.isGuest() || userInfo.isEphemeral()) {
-                // This is a user to be stopped.
-                synchronized (mLock) {
-                    stopUsersLU(oldUserId, true, null);
-                }
-                break;
+
+        UserInfo userInfo = getUserInfo(oldUserId);
+        if (userInfo.isEphemeral()) {
+            LocalServices.getService(UserManagerInternal.class).onEphemeralUserStop(oldUserId);
+        }
+        if (userInfo.isGuest() || userInfo.isEphemeral()) {
+            // This is a user to be stopped.
+            synchronized (mLock) {
+                stopUsersLU(oldUserId, true, null);
             }
         }
     }
@@ -1333,7 +1335,7 @@
         mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
         mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG,
                 newUserId, 0));
-        stopGuestOrEphemeralUserIfBackground();
+        stopGuestOrEphemeralUserIfBackground(oldUserId);
         stopBackgroundUsersIfEnforced(oldUserId);
     }
 
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index 7d3b670..10e6cad 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -25,6 +25,7 @@
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
 import android.os.ParcelableException;
+import android.os.RemoteException;
 import android.util.Slog;
 
 import com.android.server.SystemService;
@@ -86,7 +87,7 @@
 
         @Override
         public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
-                boolean withAudio, ITunerCallback callback) {
+                boolean withAudio, ITunerCallback callback) throws RemoteException {
             Slog.i(TAG, "openTuner(" + moduleId + ", _, " + withAudio + ", _)");
             enforcePolicyAccess();
             if (callback == null) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
index e5090ed..f9b35f5 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
@@ -21,6 +21,7 @@
 import android.graphics.BitmapFactory;
 import android.hardware.radio.ITuner;
 import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.ProgramList;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.os.IBinder;
@@ -249,8 +250,7 @@
         }
     }
 
-    @Override
-    public List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) {
+    List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) {
         Map<String, String> sFilter = vendorFilter;
         synchronized (mLock) {
             checkNotClosedLocked();
@@ -263,6 +263,16 @@
     }
 
     @Override
+    public void startProgramListUpdates(ProgramList.Filter filter) {
+        mTunerCallback.startProgramListUpdates(filter);
+    }
+
+    @Override
+    public void stopProgramListUpdates() {
+        mTunerCallback.stopProgramListUpdates();
+    }
+
+    @Override
     public boolean isConfigFlagSupported(int flag) {
         return flag == RadioManager.CONFIG_FORCE_ANALOG;
     }
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
index 673ff88..18f56ed 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.hardware.radio.ITuner;
 import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.ProgramList;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioMetadata;
 import android.hardware.radio.RadioTuner;
@@ -28,6 +29,10 @@
 
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 
 class TunerCallback implements ITunerCallback {
     private static final String TAG = "BroadcastRadioService.TunerCallback";
@@ -40,6 +45,8 @@
     @NonNull private final Tuner mTuner;
     @NonNull private final ITunerCallback mClientCallback;
 
+    private final AtomicReference<ProgramList.Filter> mProgramListFilter = new AtomicReference<>();
+
     TunerCallback(@NonNull Tuner tuner, @NonNull ITunerCallback clientCallback, int halRev) {
         mTuner = tuner;
         mClientCallback = clientCallback;
@@ -78,6 +85,15 @@
         mTuner.close();
     }
 
+    void startProgramListUpdates(@NonNull ProgramList.Filter filter) {
+        mProgramListFilter.set(Objects.requireNonNull(filter));
+        sendProgramListUpdate();
+    }
+
+    void stopProgramListUpdates() {
+        mProgramListFilter.set(null);
+    }
+
     @Override
     public void onError(int status) {
         dispatch(() -> mClientCallback.onError(status));
@@ -121,6 +137,28 @@
     @Override
     public void onProgramListChanged() {
         dispatch(() -> mClientCallback.onProgramListChanged());
+        sendProgramListUpdate();
+    }
+
+    private void sendProgramListUpdate() {
+        ProgramList.Filter filter = mProgramListFilter.get();
+        if (filter == null) return;
+
+        List<RadioManager.ProgramInfo> modified;
+        try {
+            modified = mTuner.getProgramList(filter.getVendorFilter());
+        } catch (IllegalStateException ex) {
+            Slog.d(TAG, "Program list not ready yet");
+            return;
+        }
+        Set<RadioManager.ProgramInfo> modifiedSet = modified.stream().collect(Collectors.toSet());
+        ProgramList.Chunk chunk = new ProgramList.Chunk(true, true, modifiedSet, null);
+        dispatch(() -> mClientCallback.onProgramListUpdated(chunk));
+    }
+
+    @Override
+    public void onProgramListUpdated(ProgramList.Chunk chunk) {
+        dispatch(() -> mClientCallback.onProgramListUpdated(chunk));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 9158ff0..fc9a5d6 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -82,7 +82,7 @@
     }
 
     public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
-        boolean withAudio, @NonNull ITunerCallback callback) {
+        boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
         Objects.requireNonNull(callback);
 
         if (!withAudio) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index 2c129bb..60a927c 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -20,15 +20,22 @@
 import android.annotation.Nullable;
 import android.hardware.broadcastradio.V2_0.AmFmBandRange;
 import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
+import android.hardware.broadcastradio.V2_0.ProgramFilter;
+import android.hardware.broadcastradio.V2_0.ProgramIdentifier;
+import android.hardware.broadcastradio.V2_0.ProgramInfo;
+import android.hardware.broadcastradio.V2_0.ProgramInfoFlags;
+import android.hardware.broadcastradio.V2_0.ProgramListChunk;
 import android.hardware.broadcastradio.V2_0.Properties;
 import android.hardware.broadcastradio.V2_0.Result;
 import android.hardware.broadcastradio.V2_0.VendorKeyValue;
+import android.hardware.radio.ProgramList;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.os.ParcelableException;
 import android.util.Slog;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -36,6 +43,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 class Convert {
     private static final String TAG = "BcRadio2Srv.convert";
@@ -78,43 +86,52 @@
         return map;
     }
 
+    private static @ProgramSelector.ProgramType int identifierTypeToProgramType(
+            @ProgramSelector.IdentifierType int idType) {
+        switch (idType) {
+            case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY:
+            case ProgramSelector.IDENTIFIER_TYPE_RDS_PI:
+                // TODO(b/69958423): verify AM/FM with frequency range
+                return ProgramSelector.PROGRAM_TYPE_FM;
+            case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT:
+                // TODO(b/69958423): verify AM/FM with frequency range
+                return ProgramSelector.PROGRAM_TYPE_FM_HD;
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC:
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
+                return ProgramSelector.PROGRAM_TYPE_DAB;
+            case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
+            case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
+                return ProgramSelector.PROGRAM_TYPE_DRMO;
+            case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID:
+            case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL:
+                return ProgramSelector.PROGRAM_TYPE_SXM;
+        }
+        if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START
+                && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) {
+            return idType;
+        }
+        return ProgramSelector.PROGRAM_TYPE_INVALID;
+    }
+
     private static @NonNull int[]
     identifierTypesToProgramTypes(@NonNull int[] idTypes) {
         Set<Integer> pTypes = new HashSet<>();
 
         for (int idType : idTypes) {
-            switch (idType) {
-                case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY:
-                case ProgramSelector.IDENTIFIER_TYPE_RDS_PI:
-                    // TODO(b/69958423): verify AM/FM with region info
-                    pTypes.add(ProgramSelector.PROGRAM_TYPE_AM);
-                    pTypes.add(ProgramSelector.PROGRAM_TYPE_FM);
-                    break;
-                case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT:
-                    // TODO(b/69958423): verify AM/FM with region info
-                    pTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD);
-                    pTypes.add(ProgramSelector.PROGRAM_TYPE_FM_HD);
-                    break;
-                case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC:
-                case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
-                case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
-                case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
-                    pTypes.add(ProgramSelector.PROGRAM_TYPE_DAB);
-                    break;
-                case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
-                case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
-                    pTypes.add(ProgramSelector.PROGRAM_TYPE_DRMO);
-                    break;
-                case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID:
-                case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL:
-                    pTypes.add(ProgramSelector.PROGRAM_TYPE_SXM);
-                    break;
-                default:
-                    break;
+            int pType = identifierTypeToProgramType(idType);
+
+            if (pType == ProgramSelector.PROGRAM_TYPE_INVALID) continue;
+
+            pTypes.add(pType);
+            if (pType == ProgramSelector.PROGRAM_TYPE_FM) {
+                // TODO(b/69958423): verify AM/FM with region info
+                pTypes.add(ProgramSelector.PROGRAM_TYPE_AM);
             }
-            if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START
-                    && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) {
-                pTypes.add(idType);
+            if (pType == ProgramSelector.PROGRAM_TYPE_FM_HD) {
+                // TODO(b/69958423): verify AM/FM with region info
+                pTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD);
             }
         }
 
@@ -189,6 +206,64 @@
                 false,  // isBgScanSupported is deprecated
                 supportedProgramTypes,
                 supportedIdentifierTypes,
-                vendorInfoFromHal(prop.vendorInfo));
+                vendorInfoFromHal(prop.vendorInfo)
+        );
+    }
+
+    static @NonNull ProgramIdentifier programIdentifierToHal(
+            @NonNull ProgramSelector.Identifier id) {
+        ProgramIdentifier hwId = new ProgramIdentifier();
+        hwId.type = id.getType();
+        hwId.value = id.getValue();
+        return hwId;
+    }
+
+    static @NonNull ProgramSelector.Identifier programIdentifierFromHal(@NonNull ProgramIdentifier id) {
+        return new ProgramSelector.Identifier(id.type, id.value);
+    }
+
+    static @NonNull ProgramSelector programSelectorFromHal(
+            @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) {
+        ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream().map(
+            id -> programIdentifierFromHal(id)).toArray(ProgramSelector.Identifier[]::new);
+
+        return new ProgramSelector(
+            identifierTypeToProgramType(sel.primaryId.type),
+            programIdentifierFromHal(sel.primaryId),
+            secondaryIds, null);
+    }
+
+    static @NonNull RadioManager.ProgramInfo programInfoFromHal(@NonNull ProgramInfo info) {
+        return new RadioManager.ProgramInfo(
+            programSelectorFromHal(info.selector),
+            (info.infoFlags & ProgramInfoFlags.TUNED) != 0,
+            (info.infoFlags & ProgramInfoFlags.STEREO) != 0,
+            false,  // TODO(b/69860743): digital
+            info.signalQuality,
+            null,  // TODO(b/69860743): metadata
+            info.infoFlags,
+            vendorInfoFromHal(info.vendorInfo)
+        );
+    }
+
+    static @NonNull ProgramFilter programFilterToHal(@NonNull ProgramList.Filter filter) {
+        ProgramFilter hwFilter = new ProgramFilter();
+
+        filter.getIdentifierTypes().stream().forEachOrdered(hwFilter.identifierTypes::add);
+        filter.getIdentifiers().stream().forEachOrdered(
+            id -> hwFilter.identifiers.add(programIdentifierToHal(id)));
+        hwFilter.includeCategories = filter.areCategoriesIncluded();
+        hwFilter.excludeModifications = filter.areModificationsExcluded();
+
+        return hwFilter;
+    }
+
+    static @NonNull ProgramList.Chunk programListChunkFromHal(@NonNull ProgramListChunk chunk) {
+        Set<RadioManager.ProgramInfo> modified = chunk.modified.stream().map(
+            info -> programInfoFromHal(info)).collect(Collectors.toSet());
+        Set<ProgramSelector.Identifier> removed = chunk.removed.stream().map(
+            id -> programIdentifierFromHal(id)).collect(Collectors.toSet());
+
+        return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
     }
 }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 45b2190..c8e15c1 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -63,20 +63,16 @@
         }
     }
 
-    public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb) {
+    public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
+            throws RemoteException {
         TunerCallback cb = new TunerCallback(Objects.requireNonNull(userCb));
         Mutable<ITunerSession> hwSession = new Mutable<>();
         MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
 
-        try {
-            mService.openSession(cb, (int result, ITunerSession session) -> {
-                hwSession.value = session;
-                halResult.value = result;
-            });
-        } catch (RemoteException ex) {
-            Slog.e(TAG, "failed to open session", ex);
-            throw new ParcelableException(ex);
-        }
+        mService.openSession(cb, (int result, ITunerSession session) -> {
+            hwSession.value = session;
+            halResult.value = result;
+        });
 
         Convert.throwOnError("openSession", halResult.value);
         Objects.requireNonNull(hwSession.value);
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
index c9084ee..ed2a1b3 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
@@ -56,7 +56,9 @@
     public void onCurrentProgramInfoChanged(ProgramInfo info) {}
 
     @Override
-    public void onProgramListUpdated(ProgramListChunk chunk) {}
+    public void onProgramListUpdated(ProgramListChunk chunk) {
+        dispatch(() -> mClientCb.onProgramListUpdated(Convert.programListChunkFromHal(chunk)));
+    }
 
     @Override
     public void onAntennaStateChange(boolean connected) {}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 8ed646a..e093c9d 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -22,6 +22,7 @@
 import android.hardware.broadcastradio.V2_0.ITunerSession;
 import android.hardware.broadcastradio.V2_0.Result;
 import android.hardware.radio.ITuner;
+import android.hardware.radio.ProgramList;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.media.AudioSystem;
@@ -184,10 +185,19 @@
     }
 
     @Override
-    public List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) {
+    public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
         synchronized (mLock) {
             checkNotClosedLocked();
-            return null;
+            int halResult = mHwSession.startProgramListUpdates(Convert.programFilterToHal(filter));
+            Convert.throwOnError("startProgramListUpdates", halResult);
+        }
+    }
+
+    @Override
+    public void stopProgramListUpdates() throws RemoteException {
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            mHwSession.stopProgramListUpdates();
         }
     }
 
@@ -226,17 +236,12 @@
     }
 
     @Override
-    public void setConfigFlag(int flag, boolean value) {
+    public void setConfigFlag(int flag, boolean value) throws RemoteException {
         Slog.v(TAG, "setConfigFlag " + ConfigFlag.toString(flag) + " = " + value);
         synchronized (mLock) {
             checkNotClosedLocked();
 
-            int halResult;
-            try {
-                halResult = mHwSession.setConfigFlag(flag, value);
-            } catch (RemoteException ex) {
-                throw new RuntimeException("Failed to set flag " + ConfigFlag.toString(flag), ex);
-            }
+            int halResult = mHwSession.setConfigFlag(flag, value);
             Convert.throwOnError("setConfigFlag", halResult);
         }
     }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index c7a4315..3c2d724 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -128,7 +128,7 @@
 
     // Length of time (in milliseconds) that an app hosting an always-on VPN is placed on
     // the device idle whitelist during service launch and VPN bootstrap.
-    private static final long VPN_LAUNCH_IDLE_WHITELIST_DURATION = 60 * 1000;
+    private static final long VPN_LAUNCH_IDLE_WHITELIST_DURATION_MS = 60 * 1000;
 
     // TODO: create separate trackers for each unique VPN to support
     // automated reconnection
@@ -183,10 +183,10 @@
     @GuardedBy("this")
     private Set<UidRange> mBlockedUsers = new ArraySet<>();
 
-    // Handle of user initiating VPN.
+    // Handle of the user initiating VPN.
     private final int mUserHandle;
 
-    // Listen to package remove and change event in this user
+    // Listen to package removal and change events (update/uninstall) for this user
     private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -197,14 +197,14 @@
             }
 
             synchronized (Vpn.this) {
-                // Avoid race that always-on package has been unset
+                // Avoid race where always-on package has been unset
                 if (!packageName.equals(getAlwaysOnPackage())) {
                     return;
                 }
 
                 final String action = intent.getAction();
-                Log.i(TAG, "Received broadcast " + action + " for always-on package " + packageName
-                        + " in user " + mUserHandle);
+                Log.i(TAG, "Received broadcast " + action + " for always-on VPN package "
+                        + packageName + " in user " + mUserHandle);
 
                 switch(action) {
                     case Intent.ACTION_PACKAGE_REPLACED:
@@ -248,7 +248,8 @@
             Log.wtf(TAG, "Problem registering observer", e);
         }
 
-        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0, NETWORKTYPE, "");
+        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0 /* subtype */, NETWORKTYPE,
+                "" /* subtypeName */);
         mNetworkCapabilities = new NetworkCapabilities();
         mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
         mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
@@ -258,7 +259,7 @@
     }
 
     /**
-     * Set if this object is responsible for watching for {@link NetworkInfo}
+     * Set whether this object is responsible for watching for {@link NetworkInfo}
      * teardown. When {@code false}, teardown is handled externally by someone
      * else.
      */
@@ -481,7 +482,6 @@
     }
 
     private void unregisterPackageChangeReceiverLocked() {
-        // register previous intent filter
         if (mIsPackageIntentReceiverRegistered) {
             mContext.unregisterReceiver(mPackageIntentReceiver);
             mIsPackageIntentReceiverRegistered = false;
@@ -582,7 +582,7 @@
             DeviceIdleController.LocalService idleController =
                     LocalServices.getService(DeviceIdleController.LocalService.class);
             idleController.addPowerSaveTempWhitelistApp(Process.myUid(), alwaysOnPackage,
-                    VPN_LAUNCH_IDLE_WHITELIST_DURATION, mUserHandle, false, "vpn");
+                    VPN_LAUNCH_IDLE_WHITELIST_DURATION_MS, mUserHandle, false, "vpn");
 
             // Start the VPN service declared in the app's manifest.
             Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE);
@@ -612,9 +612,10 @@
      * It uses {@link VpnConfig#LEGACY_VPN} as its package name, and
      * it can be revoked by itself.
      *
-     * Note: when we added VPN pre-consent in http://ag/522961 the names oldPackage
-     * and newPackage become misleading, because when an app is pre-consented, we
-     * actually prepare oldPackage, not newPackage.
+     * Note: when we added VPN pre-consent in
+     * https://android.googlesource.com/platform/frameworks/base/+/0554260
+     * the names oldPackage and newPackage became misleading, because when
+     * an app is pre-consented, we actually prepare oldPackage, not newPackage.
      *
      * Their meanings actually are:
      *
@@ -630,7 +631,7 @@
      * @param oldPackage The package name of the old VPN application
      * @param newPackage The package name of the new VPN application
      *
-     * @return true if the operation is succeeded.
+     * @return true if the operation succeeded.
      */
     public synchronized boolean prepare(String oldPackage, String newPackage) {
         if (oldPackage != null) {
@@ -639,7 +640,7 @@
                 return false;
             }
 
-            // Package is not same or old package was reinstalled.
+            // Package is not the same or old package was reinstalled.
             if (!isCurrentPreparedPackage(oldPackage)) {
                 // The package doesn't match. We return false (to obtain user consent) unless the
                 // user has already consented to that VPN package.
@@ -861,8 +862,8 @@
 
         long token = Binder.clearCallingIdentity();
         try {
-            mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE,
-                    mNetworkInfo, mNetworkCapabilities, lp, 0, networkMisc) {
+            mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */,
+                    mNetworkInfo, mNetworkCapabilities, lp, 0 /* score */, networkMisc) {
                             @Override
                             public void unwanted() {
                                 // We are user controlled, not driven by NetworkRequest.
@@ -936,7 +937,7 @@
             }
 
             ResolveInfo info = AppGlobals.getPackageManager().resolveService(intent,
-                                                                        null, 0, mUserHandle);
+                    null, 0, mUserHandle);
             if (info == null) {
                 throw new SecurityException("Cannot find " + config.user);
             }
@@ -944,7 +945,7 @@
                 throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE);
             }
         } catch (RemoteException e) {
-                throw new SecurityException("Cannot find " + config.user);
+            throw new SecurityException("Cannot find " + config.user);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -1337,7 +1338,7 @@
     }
 
     private void enforceControlPermissionOrInternalCaller() {
-        // Require caller to be either an application with CONTROL_VPN permission or a process
+        // Require the caller to be either an application with CONTROL_VPN permission or a process
         // in the system server.
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CONTROL_VPN,
                 "Unauthorized Caller");
@@ -1417,7 +1418,7 @@
     }
 
     /**
-     * This method should only be called by ConnectivityService. Because it doesn't
+     * This method should only be called by ConnectivityService because it doesn't
      * have enough data to fill VpnInfo.primaryUnderlyingIface field.
      */
     public synchronized VpnInfo getVpnInfo() {
@@ -1768,7 +1769,7 @@
      * Bringing up a VPN connection takes time, and that is all this thread
      * does. Here we have plenty of time. The only thing we need to take
      * care of is responding to interruptions as soon as possible. Otherwise
-     * requests will be piled up. This can be done in a Handler as a state
+     * requests will pile up. This could be done in a Handler as a state
      * machine, but it is much easier to read in the current form.
      */
     private class LegacyVpnRunner extends Thread {
@@ -1781,7 +1782,7 @@
         private final AtomicInteger mOuterConnection =
                 new AtomicInteger(ConnectivityManager.TYPE_NONE);
 
-        private long mTimer = -1;
+        private long mBringupStartTime = -1;
 
         /**
          * Watch for the outer connection (passing in the constructor) going away.
@@ -1861,8 +1862,8 @@
             synchronized (TAG) {
                 Log.v(TAG, "Executing");
                 try {
-                    execute();
-                    monitorDaemons();
+                    bringup();
+                    waitForDaemonsToStop();
                     interrupted(); // Clear interrupt flag if execute called exit.
                 } catch (InterruptedException e) {
                 } finally {
@@ -1883,30 +1884,27 @@
             }
         }
 
-        private void checkpoint(boolean yield) throws InterruptedException {
+        private void checkInterruptAndDelay(boolean sleepLonger) throws InterruptedException {
             long now = SystemClock.elapsedRealtime();
-            if (mTimer == -1) {
-                mTimer = now;
-                Thread.sleep(1);
-            } else if (now - mTimer <= 60000) {
-                Thread.sleep(yield ? 200 : 1);
+            if (now - mBringupStartTime <= 60000) {
+                Thread.sleep(sleepLonger ? 200 : 1);
             } else {
                 updateState(DetailedState.FAILED, "checkpoint");
-                throw new IllegalStateException("Time is up");
+                throw new IllegalStateException("VPN bringup took too long");
             }
         }
 
-        private void execute() {
-            // Catch all exceptions so we can clean up few things.
+        private void bringup() {
+            // Catch all exceptions so we can clean up a few things.
             boolean initFinished = false;
             try {
                 // Initialize the timer.
-                checkpoint(false);
+                mBringupStartTime = SystemClock.elapsedRealtime();
 
                 // Wait for the daemons to stop.
                 for (String daemon : mDaemons) {
                     while (!SystemService.isStopped(daemon)) {
-                        checkpoint(true);
+                        checkInterruptAndDelay(true);
                     }
                 }
 
@@ -1943,7 +1941,7 @@
 
                     // Wait for the daemon to start.
                     while (!SystemService.isRunning(daemon)) {
-                        checkpoint(true);
+                        checkInterruptAndDelay(true);
                     }
 
                     // Create the control socket.
@@ -1959,7 +1957,7 @@
                         } catch (Exception e) {
                             // ignore
                         }
-                        checkpoint(true);
+                        checkInterruptAndDelay(true);
                     }
                     mSockets[i].setSoTimeout(500);
 
@@ -1973,7 +1971,7 @@
                         out.write(bytes.length >> 8);
                         out.write(bytes.length);
                         out.write(bytes);
-                        checkpoint(false);
+                        checkInterruptAndDelay(false);
                     }
                     out.write(0xFF);
                     out.write(0xFF);
@@ -1989,7 +1987,7 @@
                         } catch (Exception e) {
                             // ignore
                         }
-                        checkpoint(true);
+                        checkInterruptAndDelay(true);
                     }
                 }
 
@@ -2002,7 +2000,7 @@
                             throw new IllegalStateException(daemon + " is dead");
                         }
                     }
-                    checkpoint(true);
+                    checkInterruptAndDelay(true);
                 }
 
                 // Now we are connected. Read and parse the new state.
@@ -2058,8 +2056,8 @@
                     // Set the start time
                     mConfig.startTime = SystemClock.elapsedRealtime();
 
-                    // Check if the thread is interrupted while we are waiting.
-                    checkpoint(false);
+                    // Check if the thread was interrupted while we were waiting on the lock.
+                    checkInterruptAndDelay(false);
 
                     // Check if the interface is gone while we are waiting.
                     if (jniCheck(mConfig.interfaze) == 0) {
@@ -2082,10 +2080,11 @@
         }
 
         /**
-         * Monitor the daemons we started, moving to disconnected state if the
-         * underlying services fail.
+         * Check all daemons every two seconds. Return when one of them is stopped.
+         * The caller will move to the disconnected state when this function returns,
+         * which can happen if a daemon failed or if the VPN was torn down.
          */
-        private void monitorDaemons() throws InterruptedException{
+        private void waitForDaemonsToStop() throws InterruptedException {
             if (!mNetworkInfo.isConnected()) {
                 return;
             }
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index d957ca0..40a93c1 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -22,10 +22,12 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.SparseLongArray;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -44,6 +46,9 @@
     @GuardedBy("mLock")
     private final SparseBooleanArray mStartedSyncs = new SparseBooleanArray();
 
+    @GuardedBy("mLock")
+    private final SparseLongArray mJobStartUptimes = new SparseLongArray();
+
     private final SyncLogger mLogger = SyncLogger.getInstance();
 
     /**
@@ -82,7 +87,9 @@
         synchronized (mLock) {
             final int jobId = params.getJobId();
             mJobParamsMap.put(jobId, params);
+
             mStartedSyncs.delete(jobId);
+            mJobStartUptimes.put(jobId, SystemClock.uptimeMillis());
         }
         Message m = Message.obtain();
         m.what = SyncManager.SyncHandler.MESSAGE_START_SYNC;
@@ -113,14 +120,24 @@
             final int jobId = params.getJobId();
             mJobParamsMap.remove(jobId);
 
-            if (!mStartedSyncs.get(jobId)) {
-                final String message = "Job " + jobId + " didn't start: params=" +
-                        jobParametersToString(params);
-                mLogger.log(message);
-                Slog.wtf(TAG, message);
+            final long startUptime = mJobStartUptimes.get(jobId);
+            final long nowUptime = SystemClock.uptimeMillis();
+            if (startUptime == 0) {
+                wtf("Job " + jobId + " start uptime not found: "
+                        + " params=" + jobParametersToString(params));
+            } else if ((nowUptime - startUptime) > 60 * 1000) {
+                // WTF if startSyncH() hasn't happened, *unless* onStopJob() was called too soon.
+                // (1 minute threshold.)
+                if (!mStartedSyncs.get(jobId)) {
+                    wtf("Job " + jobId + " didn't start: "
+                            + " startUptime=" + startUptime
+                            + " nowUptime=" + nowUptime
+                            + " params=" + jobParametersToString(params));
+                }
             }
 
             mStartedSyncs.delete(jobId);
+            mJobStartUptimes.delete(jobId);
         }
         Message m = Message.obtain();
         m.what = SyncManager.SyncHandler.MESSAGE_STOP_SYNC;
@@ -169,4 +186,9 @@
                     + SyncOperation.maybeCreateFromJobExtras(params.getExtras());
         }
     }
+
+    private void wtf(String message) {
+        mLogger.log(message);
+        Slog.wtf(TAG, message);
+    }
 }
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index cbb1c01..1e94e00 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -149,7 +149,7 @@
     /**
      * Start listening for brightness slider events
      *
-     * @param brightness the initial screen brightness
+     * @param initialBrightness the initial screen brightness
      */
     public void start(float initialBrightness) {
         if (DEBUG) {
@@ -219,8 +219,8 @@
                 if (includePackage) {
                     out.add(events[i]);
                 } else {
-                    BrightnessChangeEvent event = new BrightnessChangeEvent((events[i]));
-                    event.packageName = null;
+                    BrightnessChangeEvent event = new BrightnessChangeEvent((events[i]),
+                            /* redactPackage */ true);
                     out.add(event);
                 }
             }
@@ -246,7 +246,8 @@
     }
 
     private void handleBrightnessChanged(float brightness, boolean userInitiated) {
-        final BrightnessChangeEvent event;
+        BrightnessChangeEvent.Builder builder;
+
         synchronized (mDataCollectionLock) {
             if (!mStarted) {
                 // Not currently gathering brightness change information
@@ -263,9 +264,9 @@
                 return;
             }
 
-
-            event = new BrightnessChangeEvent();
-            event.timeStamp = mInjector.currentTimeMillis();
+            builder = new BrightnessChangeEvent.Builder();
+            builder.setBrightness(brightness);
+            builder.setTimeStamp(mInjector.currentTimeMillis());
 
             final int readingCount = mLastSensorReadings.size();
             if (readingCount == 0) {
@@ -273,8 +274,8 @@
                 return;
             }
 
-            event.luxValues = new float[readingCount];
-            event.luxTimestamps = new long[readingCount];
+            float[] luxValues = new float[readingCount];
+            long[] luxTimestamps = new long[readingCount];
 
             int pos = 0;
 
@@ -282,33 +283,35 @@
             long currentTimeMillis = mInjector.currentTimeMillis();
             long elapsedTimeNanos = mInjector.elapsedRealtimeNanos();
             for (LightData reading : mLastSensorReadings) {
-                event.luxValues[pos] = reading.lux;
-                event.luxTimestamps[pos] = currentTimeMillis -
+                luxValues[pos] = reading.lux;
+                luxTimestamps[pos] = currentTimeMillis -
                         TimeUnit.NANOSECONDS.toMillis(elapsedTimeNanos - reading.timestamp);
                 ++pos;
             }
+            builder.setLuxValues(luxValues);
+            builder.setLuxTimestamps(luxTimestamps);
 
-            event.batteryLevel = mLastBatteryLevel;
-            event.lastBrightness = previousBrightness;
+            builder.setBatteryLevel(mLastBatteryLevel);
+            builder.setLastBrightness(previousBrightness);
         }
 
-        event.brightness = brightness;
-
         try {
             final ActivityManager.StackInfo focusedStack = mInjector.getFocusedStack();
-            event.userId = focusedStack.userId;
-            event.packageName = focusedStack.topActivity.getPackageName();
+            builder.setUserId(focusedStack.userId);
+            builder.setPackageName(focusedStack.topActivity.getPackageName());
         } catch (RemoteException e) {
             // Really shouldn't be possible.
+            return;
         }
 
-        event.nightMode = mInjector.getSecureIntForUser(mContentResolver,
+        builder.setNightMode(mInjector.getSecureIntForUser(mContentResolver,
                 Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 0, UserHandle.USER_CURRENT)
-                == 1;
-        event.colorTemperature = mInjector.getSecureIntForUser(mContentResolver,
+                == 1);
+        builder.setColorTemperature(mInjector.getSecureIntForUser(mContentResolver,
                 Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
-                0, UserHandle.USER_CURRENT);
+                0, UserHandle.USER_CURRENT));
 
+        BrightnessChangeEvent event = builder.build();
         if (DEBUG) {
             Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
         }
@@ -457,40 +460,43 @@
                 }
                 tag = parser.getName();
                 if (TAG_EVENT.equals(tag)) {
-                    BrightnessChangeEvent event = new BrightnessChangeEvent();
+                    BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
 
                     String brightness = parser.getAttributeValue(null, ATTR_NITS);
-                    event.brightness = Float.parseFloat(brightness);
+                    builder.setBrightness(Float.parseFloat(brightness));
                     String timestamp = parser.getAttributeValue(null, ATTR_TIMESTAMP);
-                    event.timeStamp = Long.parseLong(timestamp);
-                    event.packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+                    builder.setTimeStamp(Long.parseLong(timestamp));
+                    builder.setPackageName(parser.getAttributeValue(null, ATTR_PACKAGE_NAME));
                     String user = parser.getAttributeValue(null, ATTR_USER);
-                    event.userId = mInjector.getUserId(mUserManager, Integer.parseInt(user));
+                    builder.setUserId(mInjector.getUserId(mUserManager, Integer.parseInt(user)));
                     String batteryLevel = parser.getAttributeValue(null, ATTR_BATTERY_LEVEL);
-                    event.batteryLevel = Float.parseFloat(batteryLevel);
+                    builder.setBatteryLevel(Float.parseFloat(batteryLevel));
                     String nightMode = parser.getAttributeValue(null, ATTR_NIGHT_MODE);
-                    event.nightMode = Boolean.parseBoolean(nightMode);
+                    builder.setNightMode(Boolean.parseBoolean(nightMode));
                     String colorTemperature =
                             parser.getAttributeValue(null, ATTR_COLOR_TEMPERATURE);
-                    event.colorTemperature = Integer.parseInt(colorTemperature);
+                    builder.setColorTemperature(Integer.parseInt(colorTemperature));
                     String lastBrightness = parser.getAttributeValue(null, ATTR_LAST_NITS);
-                    event.lastBrightness = Float.parseFloat(lastBrightness);
+                    builder.setLastBrightness(Float.parseFloat(lastBrightness));
 
                     String luxValue = parser.getAttributeValue(null, ATTR_LUX);
                     String luxTimestamp = parser.getAttributeValue(null, ATTR_LUX_TIMESTAMPS);
 
-                    String[] luxValues = luxValue.split(",");
-                    String[] luxTimestamps = luxTimestamp.split(",");
-                    if (luxValues.length != luxTimestamps.length) {
+                    String[] luxValuesStrings = luxValue.split(",");
+                    String[] luxTimestampsStrings = luxTimestamp.split(",");
+                    if (luxValuesStrings.length != luxTimestampsStrings.length) {
                         continue;
                     }
-                    event.luxValues = new float[luxValues.length];
-                    event.luxTimestamps = new long[luxValues.length];
+                    float[] luxValues = new float[luxValuesStrings.length];
+                    long[] luxTimestamps = new long[luxValuesStrings.length];
                     for (int i = 0; i < luxValues.length; ++i) {
-                        event.luxValues[i] = Float.parseFloat(luxValues[i]);
-                        event.luxTimestamps[i] = Long.parseLong(luxTimestamps[i]);
+                        luxValues[i] = Float.parseFloat(luxValuesStrings[i]);
+                        luxTimestamps[i] = Long.parseLong(luxTimestampsStrings[i]);
                     }
+                    builder.setLuxValues(luxValues);
+                    builder.setLuxTimestamps(luxTimestamps);
 
+                    BrightnessChangeEvent event = builder.build();
                     if (DEBUG) {
                         Slog.i(TAG, "Read event " + event.brightness
                                 + " " + event.packageName);
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 85686ae..4f53ed4 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -99,7 +99,7 @@
     private final float mProjMatrix[] = new float[16];
     private final int[] mGLBuffers = new int[2];
     private int mTexCoordLoc, mVertexLoc, mTexUnitLoc, mProjMatrixLoc, mTexMatrixLoc;
-    private int mOpacityLoc, mGammaLoc, mSaturationLoc;
+    private int mOpacityLoc, mGammaLoc;
     private int mProgram;
 
     // Vertex and corresponding texture coordinates.
@@ -245,7 +245,6 @@
 
         mOpacityLoc = GLES20.glGetUniformLocation(mProgram, "opacity");
         mGammaLoc = GLES20.glGetUniformLocation(mProgram, "gamma");
-        mSaturationLoc = GLES20.glGetUniformLocation(mProgram, "saturation");
         mTexUnitLoc = GLES20.glGetUniformLocation(mProgram, "texUnit");
 
         GLES20.glUseProgram(mProgram);
@@ -393,9 +392,8 @@
             double cos = Math.cos(Math.PI * one_minus_level);
             double sign = cos < 0 ? -1 : 1;
             float opacity = (float) -Math.pow(one_minus_level, 2) + 1;
-            float saturation = (float) Math.pow(level, 4);
             float gamma = (float) ((0.5d * sign * Math.pow(cos, 2) + 0.5d) * 0.9d + 0.1d);
-            drawFaded(opacity, 1.f / gamma, saturation);
+            drawFaded(opacity, 1.f / gamma);
             if (checkGlErrors("drawFrame")) {
                 return false;
             }
@@ -407,10 +405,9 @@
         return showSurface(1.0f);
     }
 
-    private void drawFaded(float opacity, float gamma, float saturation) {
+    private void drawFaded(float opacity, float gamma) {
         if (DEBUG) {
-            Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma +
-                        ", saturation=" + saturation);
+            Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma);
         }
         // Use shaders
         GLES20.glUseProgram(mProgram);
@@ -420,7 +417,6 @@
         GLES20.glUniformMatrix4fv(mTexMatrixLoc, 1, false, mTexMatrix, 0);
         GLES20.glUniform1f(mOpacityLoc, opacity);
         GLES20.glUniform1f(mGammaLoc, gamma);
-        GLES20.glUniform1f(mSaturationLoc, saturation);
 
         // Use textures
         GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 483b02c..23e4c9b 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display;
 
+import android.app.ActivityThread;
 import android.content.res.Resources;
 import com.android.server.LocalServices;
 import com.android.server.lights.Light;
@@ -392,7 +393,7 @@
                             | DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
                 }
 
-                final Resources res = getContext().getResources();
+                final Resources res = getOverlayContext().getResources();
                 if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
                     mInfo.name = res.getString(
                             com.android.internal.R.string.display_manager_built_in_display_name);
@@ -687,6 +688,11 @@
         }
     }
 
+    /** Supplies a context whose Resources apply runtime-overlays */
+    Context getOverlayContext() {
+        return ActivityThread.currentActivityThread().getSystemUiContext();
+    }
+
     /**
      * Keeps track of a display configuration.
      */
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index e6de07d..e158819 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -84,8 +84,6 @@
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
 
-import com.android.server.power.BatterySaverPolicy;
-
 import libcore.io.IoUtils;
 
 import java.io.File;
@@ -579,7 +577,7 @@
         final PowerSaveState result =
                 mPowerManager.getPowerSaveState(ServiceType.GPS);
         switch (result.gpsMode) {
-            case BatterySaverPolicy.GPS_MODE_DISABLED_WHEN_SCREEN_OFF:
+            case PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
                 // If we are in battery saver mode and the screen is off, disable GPS.
                 disableGps |= result.batterySaverEnabled && !mPowerManager.isInteractive();
                 break;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 516828b..ee08c38 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -79,10 +79,10 @@
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.security.keystore.UserNotAuthenticatedException;
-import android.security.recoverablekeystore.KeyEntryRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
-import android.security.recoverablekeystore.RecoverableKeyStoreLoader.RecoverableKeyStoreLoaderException;
+import android.security.keystore.EntryRecoveryData;
+import android.security.keystore.RecoveryData;
+import android.security.keystore.RecoveryMetadata;
+import android.security.keystore.RecoveryManagerException;
 import android.service.gatekeeper.GateKeeperResponse;
 import android.service.gatekeeper.IGateKeeperService;
 import android.text.TextUtils;
@@ -1968,7 +1968,7 @@
     }
 
     @Override
-    public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account) throws RemoteException {
+    public RecoveryData getRecoveryData(@NonNull byte[] account) throws RemoteException {
         return mRecoverableKeyStoreManager.getRecoveryData(account);
     }
 
@@ -1982,8 +1982,8 @@
     }
 
     @Override
-    public void setServerParameters(long serverParameters) throws RemoteException {
-        mRecoverableKeyStoreManager.setServerParameters(serverParameters);
+    public void setServerParams(byte[] serverParams) throws RemoteException {
+        mRecoverableKeyStoreManager.setServerParams(serverParams);
     }
 
     @Override
@@ -1997,7 +1997,7 @@
     }
 
     @Override
-    public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType
+    public void setRecoverySecretTypes(@NonNull @RecoveryMetadata.UserSecretType
             int[] secretTypes) throws RemoteException {
         mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes);
     }
@@ -2014,7 +2014,7 @@
     }
 
     @Override
-    public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret)
+    public void recoverySecretAvailable(@NonNull RecoveryMetadata recoverySecret)
             throws RemoteException {
         mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret);
     }
@@ -2022,7 +2022,7 @@
     @Override
     public byte[] startRecoverySession(@NonNull String sessionId,
             @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams,
-            @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets)
+            @NonNull byte[] vaultChallenge, @NonNull List<RecoveryMetadata> secrets)
             throws RemoteException {
         return mRecoverableKeyStoreManager.startRecoverySession(sessionId, verifierPublicKey,
                 vaultParams, vaultChallenge, secrets);
@@ -2030,7 +2030,7 @@
 
     @Override
     public Map<String, byte[]> recoverKeys(@NonNull String sessionId,
-            @NonNull byte[] recoveryKeyBlob, @NonNull List<KeyEntryRecoveryData> applicationKeys)
+            @NonNull byte[] recoveryKeyBlob, @NonNull List<EntryRecoveryData> applicationKeys)
             throws RemoteException {
         return mRecoverableKeyStoreManager.recoverKeys(
                 sessionId, recoveryKeyBlob, applicationKeys);
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 6079873..5fe11b1 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -16,15 +16,15 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
+import static android.security.keystore.RecoveryMetadata.TYPE_LOCKSCREEN;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.security.recoverablekeystore.KeyDerivationParameters;
-import android.security.recoverablekeystore.KeyEntryRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.security.keystore.KeyDerivationParams;
+import android.security.keystore.EntryRecoveryData;
+import android.security.keystore.RecoveryData;
+import android.security.keystore.RecoveryMetadata;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -175,7 +175,7 @@
             return;
         }
 
-        Long deviceId = mRecoverableKeyStoreDb.getServerParameters(mUserId, recoveryAgentUid);
+        byte[] deviceId = mRecoverableKeyStoreDb.getServerParams(mUserId, recoveryAgentUid);
         if (deviceId == null) {
             Log.w(TAG, "No device ID set for user " + mUserId);
             return;
@@ -232,8 +232,8 @@
         byte[] vaultParams = KeySyncUtils.packVaultParams(
                 publicKey,
                 counterId,
-                TRUSTED_HARDWARE_MAX_ATTEMPTS,
-                deviceId);
+                deviceId,
+                TRUSTED_HARDWARE_MAX_ATTEMPTS);
 
         byte[] encryptedRecoveryKey;
         try {
@@ -250,12 +250,13 @@
             return;
         }
         // TODO: store raw data in RecoveryServiceMetadataEntry and generate Parcelables later
-        KeyStoreRecoveryMetadata metadata = new KeyStoreRecoveryMetadata(
+        // TODO: use Builder.
+        RecoveryMetadata metadata = new RecoveryMetadata(
                 /*userSecretType=*/ TYPE_LOCKSCREEN,
                 /*lockScreenUiFormat=*/ getUiFormat(mCredentialType, mCredential),
-                /*keyDerivationParameters=*/ KeyDerivationParameters.createSha256Parameters(salt),
+                /*keyDerivationParams=*/ KeyDerivationParams.createSha256Params(salt),
                 /*secret=*/ new byte[0]);
-        ArrayList<KeyStoreRecoveryMetadata> metadataList = new ArrayList<>();
+        ArrayList<RecoveryMetadata> metadataList = new ArrayList<>();
         metadataList.add(metadata);
 
         int snapshotVersion = incrementSnapshotVersion(recoveryAgentUid);
@@ -263,7 +264,8 @@
         // If application keys are not updated, snapshot will not be created on next unlock.
         mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, false);
 
-        mRecoverySnapshotStorage.put(recoveryAgentUid, new KeyStoreRecoveryData(
+        // TODO: use Builder.
+        mRecoverySnapshotStorage.put(recoveryAgentUid, new RecoveryData(
                 snapshotVersion,
                 /*recoveryMetadata=*/ metadataList,
                 /*applicationKeyBlobs=*/ createApplicationKeyEntries(encryptedApplicationKeys),
@@ -306,7 +308,7 @@
      */
     private boolean shoudCreateSnapshot(int recoveryAgentUid) {
         int[] types = mRecoverableKeyStoreDb.getRecoverySecretTypes(mUserId, recoveryAgentUid);
-        if (!ArrayUtils.contains(types, KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN)) {
+        if (!ArrayUtils.contains(types, RecoveryMetadata.TYPE_LOCKSCREEN)) {
             // Only lockscreen type is supported.
             // We will need to pass extra argument to KeySyncTask to support custom pass phrase.
             return false;
@@ -329,14 +331,14 @@
      * @return The format - either pattern, pin, or password.
      */
     @VisibleForTesting
-    @KeyStoreRecoveryMetadata.LockScreenUiFormat static int getUiFormat(
+    @RecoveryMetadata.LockScreenUiFormat static int getUiFormat(
             int credentialType, String credential) {
         if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
-            return KeyStoreRecoveryMetadata.TYPE_PATTERN;
+            return RecoveryMetadata.TYPE_PATTERN;
         } else if (isPin(credential)) {
-            return KeyStoreRecoveryMetadata.TYPE_PIN;
+            return RecoveryMetadata.TYPE_PIN;
         } else {
-            return KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+            return RecoveryMetadata.TYPE_PASSWORD;
         }
     }
 
@@ -399,12 +401,12 @@
         return keyGenerator.generateKey();
     }
 
-    private static List<KeyEntryRecoveryData> createApplicationKeyEntries(
+    private static List<EntryRecoveryData> createApplicationKeyEntries(
             Map<String, byte[]> encryptedApplicationKeys) {
-        ArrayList<KeyEntryRecoveryData> keyEntries = new ArrayList<>();
+        ArrayList<EntryRecoveryData> keyEntries = new ArrayList<>();
         for (String alias : encryptedApplicationKeys.keySet()) {
             keyEntries.add(
-                    new KeyEntryRecoveryData(
+                    new EntryRecoveryData(
                             alias,
                             encryptedApplicationKeys.get(alias)));
         }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index e851d8c..b4bef17 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -37,8 +37,7 @@
 import javax.crypto.SecretKey;
 
 /**
- * Utility functions for the flow where the RecoverableKeyStoreLoader syncs keys with remote
- * storage.
+ * Utility functions for the flow where the RecoveryManager syncs keys with remote storage.
  *
  * @hide
  */
@@ -288,17 +287,17 @@
      *
      * @param thmPublicKey Public key of the trusted hardware module.
      * @param counterId ID referring to the specific counter in the hardware module.
-     * @param maxAttempts Maximum allowed guesses before trusted hardware wipes key.
      * @param deviceId ID of the device.
+     * @param maxAttempts Maximum allowed guesses before trusted hardware wipes key.
      * @return The binary vault params, ready for sync.
      */
     public static byte[] packVaultParams(
-            PublicKey thmPublicKey, long counterId, int maxAttempts, long deviceId) {
+            PublicKey thmPublicKey, long counterId, byte[] deviceId, int maxAttempts) {
         return ByteBuffer.allocate(VAULT_PARAMS_LENGTH_BYTES)
                 .order(ByteOrder.LITTLE_ENDIAN)
                 .put(SecureBox.encodePublicKey(thmPublicKey))
                 .putLong(counterId)
-                .putLong(deviceId)
+                .putLong(0L) // TODO: replace with device Id.
                 .putInt(maxAttempts)
                 .array();
     }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index a6f7766..7658178 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -16,32 +16,28 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
-        .ERROR_BAD_X509_CERTIFICATE;
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader.ERROR_DATABASE_ERROR;
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
-        .ERROR_DECRYPTION_FAILED;
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader.ERROR_INSECURE_USER;
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
-        .ERROR_KEYSTORE_INTERNAL_ERROR;
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
-        .ERROR_NOT_YET_SUPPORTED;
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
-        .ERROR_UNEXPECTED_MISSING_ALGORITHM;
+import static android.security.keystore.RecoveryManagerException.ERROR_BAD_X509_CERTIFICATE;
+import static android.security.keystore.RecoveryManagerException.ERROR_DATABASE_ERROR;
+import static android.security.keystore.RecoveryManagerException.ERROR_DECRYPTION_FAILED;
+import static android.security.keystore.RecoveryManagerException.ERROR_INSECURE_USER;
+import static android.security.keystore.RecoveryManagerException.ERROR_KEYSTORE_INTERNAL_ERROR;
+import static android.security.keystore.RecoveryManagerException.ERROR_UNEXPECTED_MISSING_ALGORITHM;
+import static android.security.keystore.RecoveryManagerException.ERROR_NO_SNAPSHOT_PENDING;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.Manifest;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.os.UserHandle;
 
-import android.security.recoverablekeystore.KeyEntryRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
-import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.security.keystore.EntryRecoveryData;
+import android.security.keystore.RecoveryData;
+import android.security.keystore.RecoveryMetadata;
+import android.security.keystore.RecoveryManager;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -69,7 +65,7 @@
 import javax.crypto.AEADBadTagException;
 
 /**
- * Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact
+ * Class with {@link RecoveryManager} API implementation and internal methods to interact
  * with {@code LockSettingsService}.
  *
  * @hide
@@ -148,6 +144,7 @@
             throws RemoteException {
         checkRecoverKeyStorePermission();
         int userId = UserHandle.getCallingUserId();
+        int uid = Binder.getCallingUid();
         // TODO: open /system/etc/security/... cert file, and check the signature on the public keys
         PublicKey publicKey;
         try {
@@ -162,7 +159,10 @@
             throw new ServiceSpecificException(
                     ERROR_BAD_X509_CERTIFICATE, "Not a valid X509 certificate.");
         }
-        mDatabase.setRecoveryServicePublicKey(userId, Binder.getCallingUid(), publicKey);
+        long updatedRows = mDatabase.setRecoveryServicePublicKey(userId, uid, publicKey);
+        if (updatedRows > 0) {
+            mDatabase.setShouldCreateSnapshot(userId, uid, true);
+        }
     }
 
     /**
@@ -171,13 +171,13 @@
      * @return recovery data
      * @hide
      */
-    public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
+    public @NonNull RecoveryData getRecoveryData(@NonNull byte[] account)
             throws RemoteException {
         checkRecoverKeyStorePermission();
         int uid = Binder.getCallingUid();
-        KeyStoreRecoveryData snapshot = mSnapshotStorage.get(uid);
+        RecoveryData snapshot = mSnapshotStorage.get(uid);
         if (snapshot == null) {
-            throw new ServiceSpecificException(RecoverableKeyStoreLoader.ERROR_NO_SNAPSHOT_PENDING);
+            throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING);
         }
         return snapshot;
     }
@@ -201,10 +201,14 @@
         throw new UnsupportedOperationException();
     }
 
-    public void setServerParameters(long serverParameters) throws RemoteException {
+    public void setServerParams(byte[] serverParams) throws RemoteException {
         checkRecoverKeyStorePermission();
         int userId = UserHandle.getCallingUserId();
-        mDatabase.setServerParameters(userId, Binder.getCallingUid(), serverParameters);
+        int uid = Binder.getCallingUid();
+        long updatedRows = mDatabase.setServerParams(userId, uid, serverParams);
+        if (updatedRows > 0) {
+            mDatabase.setShouldCreateSnapshot(userId, uid, true);
+        }
     }
 
     /**
@@ -253,11 +257,15 @@
      * @hide
      */
     public void setRecoverySecretTypes(
-            @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes)
+            @NonNull @RecoveryMetadata.UserSecretType int[] secretTypes)
             throws RemoteException {
         checkRecoverKeyStorePermission();
-        mDatabase.setRecoverySecretTypes(UserHandle.getCallingUserId(), Binder.getCallingUid(),
-            secretTypes);
+        int userId = UserHandle.getCallingUserId();
+        int uid = Binder.getCallingUid();
+        long updatedRows = mDatabase.setRecoverySecretTypes(userId, uid, secretTypes);
+        if (updatedRows > 0) {
+            mDatabase.setShouldCreateSnapshot(userId, uid, true);
+        }
     }
 
     /**
@@ -273,7 +281,7 @@
     }
 
     /**
-     * Gets secret types RecoverableKeyStoreLoaders is waiting for to create new Recovery Data.
+     * Gets secret types RecoveryManagers is waiting for to create new Recovery Data.
      *
      * @return secret types
      * @hide
@@ -284,9 +292,9 @@
     }
 
     public void recoverySecretAvailable(
-            @NonNull KeyStoreRecoveryMetadata recoverySecret) throws RemoteException {
+            @NonNull RecoveryMetadata recoverySecret) throws RemoteException {
         int uid = Binder.getCallingUid();
-        if (recoverySecret.getLockScreenUiFormat() == KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN) {
+        if (recoverySecret.getLockScreenUiFormat() == RecoveryMetadata.TYPE_LOCKSCREEN) {
             throw new SecurityException(
                     "Caller " + uid + " is not allowed to set lock screen secret");
         }
@@ -312,16 +320,13 @@
             @NonNull byte[] verifierPublicKey,
             @NonNull byte[] vaultParams,
             @NonNull byte[] vaultChallenge,
-            @NonNull List<KeyStoreRecoveryMetadata> secrets)
+            @NonNull List<RecoveryMetadata> secrets)
             throws RemoteException {
         checkRecoverKeyStorePermission();
         int uid = Binder.getCallingUid();
 
         if (secrets.size() != 1) {
-            // TODO: support multiple secrets
-            throw new ServiceSpecificException(
-                    ERROR_NOT_YET_SUPPORTED,
-                    "Only a single KeyStoreRecoveryMetadata is supported");
+            throw new UnsupportedOperationException("Only a single RecoveryMetadata is supported");
         }
 
         PublicKey publicKey;
@@ -379,7 +384,7 @@
     public Map<String, byte[]> recoverKeys(
             @NonNull String sessionId,
             @NonNull byte[] encryptedRecoveryKey,
-            @NonNull List<KeyEntryRecoveryData> applicationKeys)
+            @NonNull List<EntryRecoveryData> applicationKeys)
             throws RemoteException {
         checkRecoverKeyStorePermission();
         int uid = Binder.getCallingUid();
@@ -469,9 +474,9 @@
      */
     private Map<String, byte[]> recoverApplicationKeys(
             @NonNull byte[] recoveryKey,
-            @NonNull List<KeyEntryRecoveryData> applicationKeys) throws RemoteException {
+            @NonNull List<EntryRecoveryData> applicationKeys) throws RemoteException {
         HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
-        for (KeyEntryRecoveryData applicationKey : applicationKeys) {
+        for (EntryRecoveryData applicationKey : applicationKeys) {
             String alias = applicationKey.getAlias();
             byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
 
@@ -555,7 +560,7 @@
 
     private void checkRecoverKeyStorePermission() {
         mContext.enforceCallingOrSelfPermission(
-                RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE,
+                Manifest.permission.RECOVER_KEYSTORE,
                 "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
     }
 
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
index 54aa9f0..0042e10 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
@@ -17,7 +17,7 @@
 package com.android.server.locksettings.recoverablekeystore;
 
 import android.util.Log;
-import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.security.keystore.RecoveryManager;
 
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
@@ -97,7 +97,7 @@
                 /*nonce=*/ cipher.getIV(),
                 /*keyMaterial=*/ encryptedKeyMaterial,
                 /*platformKeyGenerationId=*/ wrappingKey.getGenerationId(),
-                RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS);
+                RecoveryManager.RECOVERY_STATUS_SYNC_IN_PROGRESS);
     }
 
     /**
@@ -107,14 +107,14 @@
      * @param keyMaterial The encrypted bytes of the key material.
      * @param platformKeyGenerationId The generation ID of the key used to wrap this key.
      *
-     * @see RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS
+     * @see RecoveryManager.RECOVERY_STATUS_SYNC_IN_PROGRESS
      * @hide
      */
     public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId) {
         mNonce = nonce;
         mKeyMaterial = keyMaterial;
         mPlatformKeyGenerationId = platformKeyGenerationId;
-        mRecoveryStatus = RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS;
+        mRecoveryStatus = RecoveryManager.RECOVERY_STATUS_SYNC_IN_PROGRESS;
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index c6f3ede..eb2da80 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -336,17 +336,8 @@
      * @hide
      */
     public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) {
-        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
-        ContentValues values = new ContentValues();
-        values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, publicKey.getEncoded());
-        String selection =
-                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
-                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
-        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
-
-        ensureRecoveryServiceMetadataEntryExists(userId, uid);
-        return db.update(
-                RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
+        return setBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY,
+                publicKey.getEncoded());
     }
 
     /**
@@ -393,63 +384,27 @@
      */
     @Nullable
     public PublicKey getRecoveryServicePublicKey(int userId, int uid) {
-        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
-
-        String[] projection = {
-                RecoveryServiceMetadataEntry._ID,
-                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
-                RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
-                RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY};
-        String selection =
-                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
-                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
-        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
-
-        try (
-                Cursor cursor = db.query(
-                        RecoveryServiceMetadataEntry.TABLE_NAME,
-                        projection,
-                        selection,
-                        selectionArguments,
-                        /*groupBy=*/ null,
-                        /*having=*/ null,
-                        /*orderBy=*/ null)
-        ) {
-            int count = cursor.getCount();
-            if (count == 0) {
-                return null;
-            }
-            if (count > 1) {
-                Log.wtf(TAG,
-                        String.format(Locale.US,
-                                "%d PublicKey entries found for userId=%d uid=%d. "
-                                        + "Should only ever be 0 or 1.", count, userId, uid));
-                return null;
-            }
-            cursor.moveToFirst();
-            int idx = cursor.getColumnIndexOrThrow(
-                    RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY);
-            if (cursor.isNull(idx)) {
-                return null;
-            }
-            byte[] keyBytes = cursor.getBlob(idx);
-            try {
-                return decodeX509Key(keyBytes);
-            } catch (InvalidKeySpecException e) {
-                Log.wtf(TAG,
-                        String.format(Locale.US,
-                                "Recovery service public key entry cannot be decoded for "
-                                        + "userId=%d uid=%d.",
-                                userId, uid));
-                return null;
-            }
+        byte[] keyBytes =
+                getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY);
+        if (keyBytes == null) {
+            return null;
+        }
+        try {
+            return decodeX509Key(keyBytes);
+        } catch (InvalidKeySpecException e) {
+            Log.wtf(TAG,
+                    String.format(Locale.US,
+                            "Recovery service public key entry cannot be decoded for "
+                                    + "userId=%d uid=%d.",
+                            userId, uid));
+            return null;
         }
     }
 
     /**
      * Updates the list of user secret types used for end-to-end encryption.
      * If no secret types are set, recovery snapshot will not be created.
-     * See {@code KeyStoreRecoveryMetadata}
+     * See {@code RecoveryMetadata}
      *
      * @param userId The userId of the profile the application is running under.
      * @param uid The uid of the application.
@@ -617,14 +572,14 @@
      *
      * @param userId The userId of the profile the application is running under.
      * @param uid The uid of the application.
-     * @param serverParameters The server parameters.
+     * @param serverParams The server parameters.
      * @return The primary key of the inserted row, or -1 if failed.
      *
      * @hide
      */
-    public long setServerParameters(int userId, int uid, long serverParameters) {
-        return setLong(userId, uid,
-                RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS, serverParameters);
+    public long setServerParams(int userId, int uid, byte[] serverParams) {
+        return setBytes(userId, uid,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS, serverParams);
     }
 
     /**
@@ -638,9 +593,8 @@
      * @hide
      */
     @Nullable
-    public Long getServerParameters(int userId, int uid) {
-        return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS);
-
+    public byte[] getServerParams(int userId, int uid) {
+        return getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS);
     }
 
     /**
@@ -704,6 +658,7 @@
         return res != null && res != 0L;
     }
 
+
     /**
      * Returns given long value from the database.
      *
@@ -785,6 +740,86 @@
     }
 
     /**
+     * Returns given binary value from the database.
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application who initialized the local recovery components.
+     * @param key from {@code RecoveryServiceMetadataEntry}
+     * @return The value that were previously set, or null if there's none.
+     *
+     * @hide
+     */
+    private byte[] getBytes(int userId, int uid, String key) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+        String[] projection = {
+                RecoveryServiceMetadataEntry._ID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
+                RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
+                key};
+        String selection =
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+        try (
+            Cursor cursor = db.query(
+                    RecoveryServiceMetadataEntry.TABLE_NAME,
+                    projection,
+                    selection,
+                    selectionArguments,
+                    /*groupBy=*/ null,
+                    /*having=*/ null,
+                    /*orderBy=*/ null)
+        ) {
+            int count = cursor.getCount();
+            if (count == 0) {
+                return null;
+            }
+            if (count > 1) {
+                Log.wtf(TAG,
+                        String.format(Locale.US,
+                                "%d entries found for userId=%d uid=%d. "
+                                        + "Should only ever be 0 or 1.", count, userId, uid));
+                return null;
+            }
+            cursor.moveToFirst();
+            int idx = cursor.getColumnIndexOrThrow(key);
+            if (cursor.isNull(idx)) {
+                return null;
+            } else {
+                return cursor.getBlob(idx);
+            }
+        }
+    }
+
+    /**
+     * Sets a binary value in the database.
+     *
+     * @param userId The userId of the profile the application is running under.
+     * @param uid The uid of the application who initialized the local recovery components.
+     * @param key defined in {@code RecoveryServiceMetadataEntry}
+     * @param value new value.
+     * @return The primary key of the inserted row, or -1 if failed.
+     *
+     * @hide
+     */
+
+    private long setBytes(int userId, int uid, String key, byte[] value) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(key, value);
+        String selection =
+                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+        ensureRecoveryServiceMetadataEntryExists(userId, uid);
+        return db.update(
+                RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
+    }
+
+    /**
      * Creates an empty row in the recovery service metadata table if such a row doesn't exist for
      * the given userId and uid, so db.update will succeed.
      */
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index 597ae4c..4ee282b 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -64,7 +64,7 @@
         static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at";
 
         /**
-         * Status of the key sync {@code RecoverableKeyStoreLoader#setRecoveryStatus}
+         * Status of the key sync {@code RecoveryManager#setRecoveryStatus}
          */
         static final String COLUMN_NAME_RECOVERY_STATUS = "recovery_status";
     }
@@ -130,6 +130,6 @@
         /**
          * The server parameters of the recovery service.
          */
-        static final String COLUMN_NAME_SERVER_PARAMETERS = "server_parameters";
+        static final String COLUMN_NAME_SERVER_PARAMS = "server_params";
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index 6eb47ee..d96671c 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -61,7 +61,7 @@
                     + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
                     + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT,"
                     + RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID + " INTEGER,"
-                    + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS + " INTEGER,"
+                    + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS + " BLOB,"
                     + "UNIQUE("
                     + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID  + ","
                     + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + "))";
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
index 011b374..158b1e3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
@@ -17,7 +17,7 @@
 package com.android.server.locksettings.recoverablekeystore.storage;
 
 import android.annotation.Nullable;
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.security.keystore.RecoveryData;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -34,12 +34,12 @@
  */
 public class RecoverySnapshotStorage {
     @GuardedBy("this")
-    private final SparseArray<KeyStoreRecoveryData> mSnapshotByUid = new SparseArray<>();
+    private final SparseArray<RecoveryData> mSnapshotByUid = new SparseArray<>();
 
     /**
      * Sets the latest {@code snapshot} for the recovery agent {@code uid}.
      */
-    public synchronized void put(int uid, KeyStoreRecoveryData snapshot) {
+    public synchronized void put(int uid, RecoveryData snapshot) {
         mSnapshotByUid.put(uid, snapshot);
     }
 
@@ -47,7 +47,7 @@
      * Returns the latest snapshot for the recovery agent {@code uid}, or null if none exists.
      */
     @Nullable
-    public synchronized KeyStoreRecoveryData get(int uid) {
+    public synchronized RecoveryData get(int uid) {
         return mSnapshotByUid.get(uid);
     }
 
diff --git a/services/core/java/com/android/server/net/watchlist/OWNERS b/services/core/java/com/android/server/net/watchlist/OWNERS
new file mode 100644
index 0000000..a3d4b85
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/OWNERS
@@ -0,0 +1,3 @@
+rickywai@google.com
+alanstokes@google.com
+simonjw@google.com
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 575c44d..16b9257 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2917,12 +2917,34 @@
             }
         }
 
+        /**
+         * Sets the notification policy.  Apps that target API levels below
+         * {@link android.os.Build.VERSION_CODES#P} cannot make DND silence
+         * {@link Policy#PRIORITY_CATEGORY_ALARMS} or
+         * {@link Policy#PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER}
+         */
         @Override
         public void setNotificationPolicy(String pkg, Policy policy) {
             enforcePolicyAccess(pkg, "setNotificationPolicy");
             final long identity = Binder.clearCallingIdentity();
             try {
+                final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkg,
+                        0, UserHandle.getUserId(MY_UID));
+
+                if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.O_MR1) {
+                    Policy currPolicy = mZenModeHelper.getNotificationPolicy();
+
+                    int priorityCategories = policy.priorityCategories
+                            | (currPolicy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS)
+                            | (currPolicy.priorityCategories &
+                            Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER);
+                    policy = new Policy(priorityCategories,
+                            policy.priorityCallSenders, policy.priorityMessageSenders,
+                            policy.suppressedVisualEffects);
+                }
+
                 mZenModeHelper.setNotificationPolicy(policy);
+            } catch (RemoteException e) {
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 700ccad..b5d2844 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -822,21 +822,24 @@
 
     @VisibleForTesting
     protected void applyRestrictions() {
-        final boolean zen = mZenMode != Global.ZEN_MODE_OFF;
+        final boolean zenPriorityOnly = mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        final boolean zenSilence  = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
+        final boolean zenAlarmsOnly = mZenMode == Global.ZEN_MODE_ALARMS;
 
         // notification restrictions
         final boolean muteNotifications =
                 (mSuppressedEffects & SUPPRESSED_EFFECT_NOTIFICATIONS) != 0;
         // call restrictions
-        final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers
+        final boolean muteCalls = zenAlarmsOnly
+                || (zenPriorityOnly && !mConfig.allowCalls && !mConfig.allowRepeatCallers)
                 || (mSuppressedEffects & SUPPRESSED_EFFECT_CALLS) != 0;
         // alarm restrictions
-        final boolean muteAlarms = zen && !mConfig.allowAlarms;
+        final boolean muteAlarms = zenPriorityOnly && !mConfig.allowAlarms;
         // alarm restrictions
-        final boolean muteMediaAndSystemSounds = zen && !mConfig.allowMediaSystemOther;
+        final boolean muteMediaAndSystemSounds = zenPriorityOnly && !mConfig.allowMediaSystemOther;
         // total silence restrictions
-        final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
-                || areAllBehaviorSoundsMuted();
+        final boolean muteEverything = zenSilence
+                || (zenPriorityOnly && areAllBehaviorSoundsMuted());
 
         for (int usage : AudioAttributes.SDK_USAGES) {
             final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index c964f91..af20cd7 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -49,7 +49,6 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
-import com.android.server.pm.permission.BasePermission;
 
 import libcore.io.IoUtils;
 import org.xmlpull.v1.XmlPullParser;
@@ -302,7 +301,7 @@
             // into account but also allow the value from the old computation to avoid
             // data loss.
             final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
-                    pkg.mSignatures);
+                    pkg.mSigningDetails.signatures);
             final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest(
                     signaturesSha256Digests);
 
@@ -313,7 +312,7 @@
             }
 
             // For backwards compatibility we accept match based on first signature
-            if (pkg.mSignatures.length > 1 && currentCookieFile.equals(computeInstantCookieFile(
+            if (pkg.mSigningDetails.signatures.length > 1 && currentCookieFile.equals(computeInstantCookieFile(
                     pkg.packageName, signaturesSha256Digests[0], userId))) {
                 return;
             }
@@ -1176,12 +1175,13 @@
             // We prefer the modern computation procedure where all certs are taken
             // into account and delete the file derived via the legacy hash computation.
             File newCookieFile = computeInstantCookieFile(pkg.packageName,
-                    PackageUtils.computeSignaturesSha256Digest(pkg.mSignatures), userId);
-            if (pkg.mSignatures.length > 0) {
-                File oldCookieFile = peekInstantCookieFile(pkg.packageName, userId);
-                if (oldCookieFile != null && !newCookieFile.equals(oldCookieFile)) {
-                    oldCookieFile.delete();
-                }
+                    PackageUtils.computeSignaturesSha256Digest(pkg.mSigningDetails.signatures), userId);
+            if (!pkg.mSigningDetails.hasSignatures()) {
+                Slog.wtf(LOG_TAG, "Parsed Instant App contains no valid signatures!");
+            }
+            File oldCookieFile = peekInstantCookieFile(pkg.packageName, userId);
+            if (oldCookieFile != null && !newCookieFile.equals(oldCookieFile)) {
+                oldCookieFile.delete();
             }
             cancelPendingPersistLPw(pkg, userId);
             addPendingPersistCookieLPw(userId, pkg, cookie, newCookieFile);
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index fca9585..93d3b77 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -188,7 +188,7 @@
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     "Passed invalid package to keyset validation.");
         }
-        ArraySet<PublicKey> signingKeys = pkg.mSigningKeys;
+        ArraySet<PublicKey> signingKeys = pkg.mSigningDetails.publicKeys;
         if (signingKeys == null || !(signingKeys.size() > 0) || signingKeys.contains(null)) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     "Package has invalid signing-key-set.");
@@ -223,7 +223,7 @@
         PackageSetting ps = mPackages.get(pkg.packageName);
         Preconditions.checkNotNull(ps, "pkg: " + pkg.packageName
                     + "does not have a corresponding entry in mPackages.");
-        addSigningKeySetToPackageLPw(ps, pkg.mSigningKeys);
+        addSigningKeySetToPackageLPw(ps, pkg.mSigningDetails.publicKeys);
         if (pkg.mKeySetMapping != null) {
             addDefinedKeySetsToPackageLPw(ps, pkg.mKeySetMapping);
             if (pkg.mUpgradeKeySets != null) {
@@ -371,7 +371,7 @@
         long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
         for (int i = 0; i < upgradeKeySets.length; i++) {
             Set<PublicKey> upgradeSet = getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
-            if (upgradeSet != null && newPkg.mSigningKeys.containsAll(upgradeSet)) {
+            if (upgradeSet != null && newPkg.mSigningDetails.publicKeys.containsAll(upgradeSet)) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5577de8..a43818a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -58,7 +58,6 @@
 import android.content.pm.PackageParser.ApkLite;
 import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.Signature;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Binder;
@@ -84,6 +83,7 @@
 import android.util.ExceptionUtils;
 import android.util.MathUtils;
 import android.util.Slog;
+import android.util.apk.ApkSignatureVerifier;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.NativeLibraryHelper;
@@ -107,7 +107,7 @@
 import java.io.FileFilter;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -227,9 +227,7 @@
     @GuardedBy("mLock")
     private long mVersionCode;
     @GuardedBy("mLock")
-    private Signature[] mSignatures;
-    @GuardedBy("mLock")
-    private Certificate[][] mCertificates;
+    private PackageParser.SigningDetails mSigningDetails;
 
     /**
      * Path to the validated base APK for this session, which may point at an
@@ -857,7 +855,7 @@
         }
 
         Preconditions.checkNotNull(mPackageName);
-        Preconditions.checkNotNull(mSignatures);
+        Preconditions.checkNotNull(mSigningDetails);
         Preconditions.checkNotNull(mResolvedBaseFile);
 
         if (needToAskForPermissionsLocked()) {
@@ -938,7 +936,7 @@
 
         mRelinquished = true;
         mPm.installStage(mPackageName, stageDir, localObserver, params,
-                mInstallerPackageName, mInstallerUid, user, mCertificates);
+                mInstallerPackageName, mInstallerUid, user, mSigningDetails);
     }
 
     /**
@@ -957,7 +955,7 @@
             throws PackageManagerException {
         mPackageName = null;
         mVersionCode = -1;
-        mSignatures = null;
+        mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
 
         mResolvedBaseFile = null;
         mResolvedStagedFiles.clear();
@@ -990,11 +988,8 @@
         for (File addedFile : addedFiles) {
             final ApkLite apk;
             try {
-                int flags = PackageParser.PARSE_COLLECT_CERTIFICATES;
-                if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
-                    flags |= PackageParser.PARSE_IS_EPHEMERAL;
-                }
-                apk = PackageParser.parseApkLite(addedFile, flags);
+                apk = PackageParser.parseApkLite(
+                        addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
             } catch (PackageParserException e) {
                 throw PackageManagerException.from(e);
             }
@@ -1009,9 +1004,8 @@
                 mPackageName = apk.packageName;
                 mVersionCode = apk.getLongVersionCode();
             }
-            if (mSignatures == null) {
-                mSignatures = apk.signatures;
-                mCertificates = apk.certificates;
+            if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
+                mSigningDetails = apk.signingDetails;
             }
 
             assertApkConsistentLocked(String.valueOf(addedFile), apk);
@@ -1060,8 +1054,15 @@
                 mPackageName = pkgInfo.packageName;
                 mVersionCode = pkgInfo.getLongVersionCode();
             }
-            if (mSignatures == null) {
-                mSignatures = pkgInfo.signatures;
+            if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
+                try {
+                    mSigningDetails = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts(
+                            pkgInfo.applicationInfo.sourceDir,
+                            PackageParser.SigningDetails.SignatureSchemeVersion.JAR);
+                } catch (PackageParserException e) {
+                    throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+                            "Couldn't obtain signatures from base APK");
+                }
             }
         }
 
@@ -1155,7 +1156,7 @@
                     + " version code " + apk.versionCode + " inconsistent with "
                     + mVersionCode);
         }
-        if (!Signature.areExactMatch(mSignatures, apk.signatures)) {
+        if (!mSigningDetails.signaturesMatchExactly(apk.signingDetails)) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     tag + " signatures are inconsistent");
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 44aad44..1e2f2b2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import static android.Manifest.permission.DELETE_PACKAGES;
+import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
 import static android.Manifest.permission.INSTALL_PACKAGES;
 import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
@@ -167,7 +168,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
-import android.content.pm.PackageManager.PackageInfoFlags;
 import android.content.pm.PackageManagerInternal.PackageListObserver;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ActivityIntentInfo;
@@ -176,6 +176,7 @@
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.PackageParser.ParseFlags;
 import android.content.pm.PackageParser.ServiceIntentInfo;
+import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.PackageStats;
 import android.content.pm.PackageUserState;
 import android.content.pm.ParceledListSlice;
@@ -337,7 +338,6 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
 import java.security.SecureRandom;
-import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -2748,7 +2748,6 @@
                 for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {
                     PackageParser.Package deletedPkg = mPackages.get(deletedAppName);
                     mSettings.removeDisabledSystemPackageLPw(deletedAppName);
-
                     final String msg;
                     if (deletedPkg == null) {
                         // should have found an update, but, we didn't; remove everything
@@ -5360,7 +5359,7 @@
                     || filterAppAccessLPr(ps2, callingUid, callingUserId)) {
                 return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
             }
-            return compareSignatures(p1.mSignatures, p2.mSignatures);
+            return compareSignatures(p1.mSigningDetails.signatures, p2.mSigningDetails.signatures);
         }
     }
 
@@ -8184,36 +8183,36 @@
     }
 
     private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg,
-            final @ParseFlags int parseFlags) throws PackageManagerException {
+            final @ParseFlags int parseFlags, boolean forceCollect) throws PackageManagerException {
         // When upgrading from pre-N MR1, verify the package time stamp using the package
         // directory and not the APK file.
         final long lastModifiedTime = mIsPreNMR1Upgrade
                 ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg);
-        if (ps != null
+        if (ps != null && !forceCollect
                 && ps.codePathString.equals(pkg.codePath)
                 && ps.timeStamp == lastModifiedTime
                 && !isCompatSignatureUpdateNeeded(pkg)
                 && !isRecoverSignatureUpdateNeeded(pkg)) {
-            long mSigningKeySetId = ps.keySetData.getProperSigningKeySet();
-            final KeySetManagerService ksms = mSettings.mKeySetManagerService;
-            ArraySet<PublicKey> signingKs;
-            synchronized (mPackages) {
-                signingKs = ksms.getPublicKeysFromKeySetLPr(mSigningKeySetId);
-            }
             if (ps.signatures.mSignatures != null
                     && ps.signatures.mSignatures.length != 0
-                    && signingKs != null) {
-                // Optimization: reuse the existing cached certificates
+                    && ps.signatures.mSignatureSchemeVersion != SignatureSchemeVersion.UNKNOWN) {
+                // Optimization: reuse the existing cached signing data
                 // if the package appears to be unchanged.
-                pkg.mSignatures = ps.signatures.mSignatures;
-                pkg.mSigningKeys = signingKs;
-                return;
+                try {
+                    pkg.mSigningDetails = new PackageParser.SigningDetails(ps.signatures.mSignatures,
+                            ps.signatures.mSignatureSchemeVersion);
+                    return;
+                } catch (CertificateException e) {
+                    Slog.e(TAG, "Attempt to read public keys from persisted signatures failed for "
+                                    + ps.name, e);
+                }
             }
 
             Slog.w(TAG, "PackageSetting for " + ps.name
                     + " is missing signatures.  Collecting certs again to recover them.");
         } else {
-            Slog.i(TAG, toString() + " changed; collecting certs");
+            Slog.i(TAG, pkg.codePath + " changed; collecting certs" +
+                    (forceCollect ? " (forced)" : ""));
         }
 
         try {
@@ -8293,14 +8292,14 @@
         }
 
         // Scan the parent
-        PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, parseFlags,
+        PackageParser.Package scannedPkg = addForInitLI(pkg, parseFlags,
                 scanFlags, currentTime, user);
 
         // Scan the children
         final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
         for (int i = 0; i < childCount; i++) {
             PackageParser.Package childPackage = pkg.childPackages.get(i);
-            scanPackageInternalLI(childPackage, parseFlags, scanFlags,
+            addForInitLI(childPackage, parseFlags, scanFlags,
                     currentTime, user);
         }
 
@@ -8313,50 +8312,85 @@
     }
 
     /**
-     *  Scans a package and returns the newly parsed package.
-     *  @throws PackageManagerException on a parse error.
+     * Adds a new package to the internal data structures during platform initialization.
+     * <p>After adding, the package is known to the system and available for querying.
+     * <p>For packages located on the device ROM [eg. packages located in /system, /vendor,
+     * etc...], additional checks are performed. Basic verification [such as ensuring
+     * matching signatures, checking version codes, etc...] occurs if the package is
+     * identical to a previously known package. If the package fails a signature check,
+     * the version installed on /data will be removed. If the version of the new package
+     * is less than or equal than the version on /data, it will be ignored.
+     * <p>Regardless of the package location, the results are applied to the internal
+     * structures and the package is made available to the rest of the system.
+     * <p>NOTE: The return value should be removed. It's the passed in package object.
      */
-    private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg,
+    private PackageParser.Package addForInitLI(PackageParser.Package pkg,
             @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
             @Nullable UserHandle user)
                     throws PackageManagerException {
-        PackageSetting ps = null;
-        PackageSetting updatedPs;
-        // reader
-        synchronized (mPackages) {
-            // Look to see if we already know about this package.
-            String oldName = mSettings.getRenamedPackageLPr(pkg.packageName);
-            if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {
-                // This package has been renamed to its original name.  Let's
-                // use that.
-                ps = mSettings.getPackageLPr(oldName);
-            }
-            // If there was no original package, see one for the real package name.
-            if (ps == null) {
-                ps = mSettings.getPackageLPr(pkg.packageName);
-            }
-            // Check to see if this package could be hiding/updating a system
-            // package.  Must look for it either under the original or real
-            // package name depending on our state.
-            updatedPs = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
-            if (DEBUG_INSTALL && updatedPs != null) Slog.d(TAG, "updatedPkg = " + updatedPs);
+        final boolean scanSystemPartition = (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0;
+        final String renamedPkgName;
+        final PackageSetting disabledPkgSetting;
+        final boolean isSystemPkgUpdated;
+        final boolean pkgAlreadyExists;
+        PackageSetting pkgSetting;
 
-            // If this is a package we don't know about on the system partition, we
-            // may need to remove disabled child packages on the system partition
-            // or may need to not add child packages if the parent apk is updated
-            // on the data partition and no longer defines this child package.
-            if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
-                // If this is a parent package for an updated system app and this system
-                // app got an OTA update which no longer defines some of the child packages
-                // we have to prune them from the disabled system packages.
-                PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(pkg.packageName);
-                if (disabledPs != null) {
+        // NOTE: installPackageLI() has the same code to setup the package's
+        // application info. This probably should be done lower in the call
+        // stack [such as scanPackageOnly()]. However, we verify the application
+        // info prior to that [in scanPackageNew()] and thus have to setup
+        // the application info early.
+        pkg.setApplicationVolumeUuid(pkg.volumeUuid);
+        pkg.setApplicationInfoCodePath(pkg.codePath);
+        pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
+        pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
+        pkg.setApplicationInfoResourcePath(pkg.codePath);
+        pkg.setApplicationInfoBaseResourcePath(pkg.baseCodePath);
+        pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
+
+        synchronized (mPackages) {
+            renamedPkgName = mSettings.getRenamedPackageLPr(pkg.mRealPackage);
+            final String realPkgName = getRealPackageName(pkg, renamedPkgName);
+            if (realPkgName != null) {
+                ensurePackageRenamed(pkg, renamedPkgName);
+            }
+            final PackageSetting originalPkgSetting = getOriginalPackageLocked(pkg, renamedPkgName);
+            final PackageSetting installedPkgSetting = mSettings.getPackageLPr(pkg.packageName);
+            pkgSetting = originalPkgSetting == null ? installedPkgSetting : originalPkgSetting;
+            pkgAlreadyExists = pkgSetting != null;
+            final String disabledPkgName = pkgAlreadyExists ? pkgSetting.name : pkg.packageName;
+            disabledPkgSetting = mSettings.getDisabledSystemPkgLPr(disabledPkgName);
+            isSystemPkgUpdated = disabledPkgSetting != null;
+
+            if (DEBUG_INSTALL && isSystemPkgUpdated) {
+                Slog.d(TAG, "updatedPkg = " + disabledPkgSetting);
+            }
+
+            final SharedUserSetting sharedUserSetting = (pkg.mSharedUserId != null)
+                    ? mSettings.getSharedUserLPw(pkg.mSharedUserId,
+                            0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true)
+                    : null;
+            if (DEBUG_PACKAGE_SCANNING
+                    && (parseFlags & PackageParser.PARSE_CHATTY) != 0
+                    && sharedUserSetting != null) {
+                Log.d(TAG, "Shared UserID " + pkg.mSharedUserId
+                        + " (uid=" + sharedUserSetting.userId + "):"
+                        + " packages=" + sharedUserSetting.packages);
+            }
+
+            if (scanSystemPartition) {
+                // Potentially prune child packages. If the application on the /system
+                // partition has been updated via OTA, but, is still disabled by a
+                // version on /data, cycle through all of its children packages and
+                // remove children that are no longer defined.
+                if (isSystemPkgUpdated) {
                     final int scannedChildCount = (pkg.childPackages != null)
                             ? pkg.childPackages.size() : 0;
-                    final int disabledChildCount = disabledPs.childPackageNames != null
-                            ? disabledPs.childPackageNames.size() : 0;
+                    final int disabledChildCount = disabledPkgSetting.childPackageNames != null
+                            ? disabledPkgSetting.childPackageNames.size() : 0;
                     for (int i = 0; i < disabledChildCount; i++) {
-                        String disabledChildPackageName = disabledPs.childPackageNames.get(i);
+                        String disabledChildPackageName =
+                                disabledPkgSetting.childPackageNames.get(i);
                         boolean disabledPackageAvailable = false;
                         for (int j = 0; j < scannedChildCount; j++) {
                             PackageParser.Package childPkg = pkg.childPackages.get(j);
@@ -8364,261 +8398,119 @@
                                 disabledPackageAvailable = true;
                                 break;
                             }
-                         }
-                         if (!disabledPackageAvailable) {
-                             mSettings.removeDisabledSystemPackageLPw(disabledChildPackageName);
-                         }
-                    }
-                }
-            }
-        }
-
-        final boolean isUpdatedPkg = updatedPs != null;
-        final boolean isUpdatedSystemPkg = isUpdatedPkg && (scanFlags & SCAN_AS_SYSTEM) != 0;
-        boolean isUpdatedPkgBetter = false;
-        // First check if this is a system package that may involve an update
-        if (isUpdatedSystemPkg) {
-            // If new package is not located in "/system/priv-app" (e.g. due to an OTA),
-            // it needs to drop FLAG_PRIVILEGED.
-            if (locationIsPrivileged(pkg.codePath)) {
-                updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
-            } else {
-                updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
-            }
-            // If new package is not located in "/oem" (e.g. due to an OTA),
-            // it needs to drop FLAG_OEM.
-            if (locationIsOem(pkg.codePath)) {
-                updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM;
-            } else {
-                updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM;
-            }
-            // If new package is not located in "/vendor" (e.g. due to an OTA),
-            // it needs to drop FLAG_VENDOR.
-            if (locationIsVendor(pkg.codePath)) {
-                updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_VENDOR;
-            } else {
-                updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_VENDOR;
-            }
-
-            if (ps != null && !ps.codePathString.equals(pkg.codePath)) {
-                // The path has changed from what was last scanned...  check the
-                // version of the new path against what we have stored to determine
-                // what to do.
-                if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);
-                if (pkg.getLongVersionCode() <= ps.versionCode) {
-                    // The system package has been updated and the code path does not match
-                    // Ignore entry. Skip it.
-                    if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + pkg.codePath
-                            + " ignored: updated version " + ps.versionCode
-                            + " better than this " + pkg.getLongVersionCode());
-                    if (!updatedPs.codePathString.equals(pkg.codePath)) {
-                        Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg "
-                                + ps.name + " changing from " + updatedPs.codePathString
-                                + " to " + pkg.codePath);
-                        final File codePath = new File(pkg.codePath);
-                        updatedPs.codePath = codePath;
-                        updatedPs.codePathString = pkg.codePath;
-                        updatedPs.resourcePath = codePath;
-                        updatedPs.resourcePathString = pkg.codePath;
-                    }
-                    updatedPs.pkg = pkg;
-                    updatedPs.versionCode = pkg.getLongVersionCode();
-
-                    // Update the disabled system child packages to point to the package too.
-                    final int childCount = updatedPs.childPackageNames != null
-                            ? updatedPs.childPackageNames.size() : 0;
-                    for (int i = 0; i < childCount; i++) {
-                        String childPackageName = updatedPs.childPackageNames.get(i);
-                        PackageSetting updatedChildPkg = mSettings.getDisabledSystemPkgLPr(
-                                childPackageName);
-                        if (updatedChildPkg != null) {
-                            updatedChildPkg.pkg = pkg;
-                            updatedChildPkg.versionCode = pkg.getLongVersionCode();
+                        }
+                        if (!disabledPackageAvailable) {
+                            mSettings.removeDisabledSystemPackageLPw(disabledChildPackageName);
                         }
                     }
-                } else {
-                    // The current app on the system partition is better than
-                    // what we have updated to on the data partition; switch
-                    // back to the system partition version.
-                    // At this point, its safely assumed that package installation for
-                    // apps in system partition will go through. If not there won't be a working
-                    // version of the app
-                    // writer
-                    synchronized (mPackages) {
-                        // Just remove the loaded entries from package lists.
-                        mPackages.remove(ps.name);
-                    }
-
-                    logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath
-                            + " reverting from " + ps.codePathString
-                            + ": new version " + pkg.getLongVersionCode()
-                            + " better than installed " + ps.versionCode);
-
-                    InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
-                            ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
-                    synchronized (mInstallLock) {
-                        args.cleanUpResourcesLI();
-                    }
-                    synchronized (mPackages) {
-                        mSettings.enableSystemPackageLPw(ps.name);
-                    }
-                    isUpdatedPkgBetter = true;
+                    // we're updating the disabled package, so, scan it as the package setting
+                    final ScanRequest request = new ScanRequest(pkg, sharedUserSetting,
+                            disabledPkgSetting /* pkgSetting */, null /* disabledPkgSetting */,
+                            null /* originalPkgSetting */, null, parseFlags, scanFlags,
+                            (pkg == mPlatformPackage), user);
+                    scanPackageOnlyLI(request, mFactoryTest, -1L);
                 }
             }
         }
 
-        String resourcePath = null;
-        String baseResourcePath = null;
-        if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !isUpdatedPkgBetter) {
-            if (ps != null && ps.resourcePathString != null) {
-                resourcePath = ps.resourcePathString;
-                baseResourcePath = ps.resourcePathString;
-            } else {
-                // Should not happen at all. Just log an error.
-                Slog.e(TAG, "Resource path not set for package " + pkg.packageName);
+        final boolean newPkgChangedPaths =
+                pkgAlreadyExists && !pkgSetting.codePathString.equals(pkg.codePath);
+        final boolean newPkgVersionGreater =
+                pkgAlreadyExists && pkg.getLongVersionCode() > pkgSetting.versionCode;
+        final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated
+                && newPkgChangedPaths && newPkgVersionGreater;
+        if (isSystemPkgBetter) {
+            // The version of the application on /system is greater than the version on
+            // /data. Switch back to the application on /system.
+            // It's safe to assume the application on /system will correctly scan. If not,
+            // there won't be a working copy of the application.
+            synchronized (mPackages) {
+                // just remove the loaded entries from package lists
+                mPackages.remove(pkgSetting.name);
             }
-        } else {
-            resourcePath = pkg.codePath;
-            baseResourcePath = pkg.baseCodePath;
+
+            logCriticalInfo(Log.WARN,
+                    "System package updated;"
+                    + " name: " + pkgSetting.name
+                    + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
+                    + "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
+
+            final InstallArgs args = createInstallArgsForExisting(
+                    packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString,
+                    pkgSetting.resourcePathString, getAppDexInstructionSets(pkgSetting));
+            args.cleanUpResourcesLI();
+            synchronized (mPackages) {
+                mSettings.enableSystemPackageLPw(pkgSetting.name);
+            }
         }
 
-        // Set application objects path explicitly.
-        pkg.setApplicationVolumeUuid(pkg.volumeUuid);
-        pkg.setApplicationInfoCodePath(pkg.codePath);
-        pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
-        pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
-        pkg.setApplicationInfoResourcePath(resourcePath);
-        pkg.setApplicationInfoBaseResourcePath(baseResourcePath);
-        pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
-
-        // throw an exception if we have an update to a system application, but, it's not more
-        // recent than the package we've already scanned
-        if (isUpdatedSystemPkg && !isUpdatedPkgBetter) {
-            // Set CPU Abis to application info.
-            if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) {
-                final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPs);
-                derivePackageAbi(pkg, cpuAbiOverride, false);
-            } else {
-                pkg.applicationInfo.primaryCpuAbi = updatedPs.primaryCpuAbiString;
-                pkg.applicationInfo.secondaryCpuAbi = updatedPs.secondaryCpuAbiString;
-            }
-            pkg.mExtras = updatedPs;
-
+        if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) {
+            // The version of the application on the /system partition is less than or
+            // equal to the version on the /data partition. Throw an exception and use
+            // the application already installed on the /data partition.
             throw new PackageManagerException(Log.WARN, "Package " + pkg.packageName + " at "
-                    + pkg.codePath + " ignored: updated version " + updatedPs.versionCode
+                    + pkg.codePath + " ignored: updated version " + disabledPkgSetting.versionCode
                     + " better than this " + pkg.getLongVersionCode());
         }
 
-        if (isUpdatedPkg) {
-            // updated system applications don't initially have the SCAN_AS_SYSTEM flag set
-            scanFlags |= SCAN_AS_SYSTEM;
+        // Verify certificates against what was last scanned. If it is an updated priv app, we will
+        // force the verification. Full apk verification will happen unless apk verity is set up for
+        // the file. In that case, only small part of the apk is verified upfront.
+        collectCertificatesLI(pkgSetting, pkg, parseFlags,
+                PackageManagerServiceUtils.isApkVerificationForced(disabledPkgSetting));
 
-            // An updated privileged application will not have the PARSE_IS_PRIVILEGED
-            // flag set initially
-            if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
-                scanFlags |= SCAN_AS_PRIVILEGED;
-            }
-
-            // An updated OEM app will not have the SCAN_AS_OEM
-            // flag set initially
-            if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
-                scanFlags |= SCAN_AS_OEM;
-            }
-
-            // An updated vendor app will not have the SCAN_AS_VENDOR
-            // flag set initially
-            if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0) {
-                scanFlags |= SCAN_AS_VENDOR;
-            }
-        }
-
-        // Verify certificates against what was last scanned
-        collectCertificatesLI(ps, pkg, parseFlags);
-
-        /*
-         * A new system app appeared, but we already had a non-system one of the
-         * same name installed earlier.
-         */
         boolean shouldHideSystemApp = false;
-        if (!isUpdatedPkg && ps != null
-                && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
-            /*
-             * Check to make sure the signatures match first. If they don't,
-             * wipe the installed application and its data.
-             */
-            if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
+        // A new application appeared on /system, but, we already have a copy of
+        // the application installed on /data.
+        if (scanSystemPartition && !isSystemPkgUpdated && pkgAlreadyExists
+                && !pkgSetting.isSystem()) {
+            // if the signatures don't match, wipe the installed application and its data
+            if (compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSigningDetails.signatures)
                     != PackageManager.SIGNATURE_MATCH) {
-                logCriticalInfo(Log.WARN, "Package " + ps.name + " appeared on system, but"
-                        + " signatures don't match existing userdata copy; removing");
+                logCriticalInfo(Log.WARN,
+                        "System package signature mismatch;"
+                        + " name: " + pkgSetting.name);
                 try (PackageFreezer freezer = freezePackage(pkg.packageName,
                         "scanPackageInternalLI")) {
                     deletePackageLIF(pkg.packageName, null, true, null, 0, null, false, null);
                 }
-                ps = null;
-            } else {
-                /*
-                 * If the newly-added system app is an older version than the
-                 * already installed version, hide it. It will be scanned later
-                 * and re-added like an update.
-                 */
-                if (pkg.getLongVersionCode() <= ps.versionCode) {
-                    shouldHideSystemApp = true;
-                    logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + pkg.codePath
-                            + " but new version " + pkg.getLongVersionCode()
-                            + " better than installed " + ps.versionCode + "; hiding system");
-                } else {
-                    /*
-                     * The newly found system app is a newer version that the
-                     * one previously installed. Simply remove the
-                     * already-installed application and replace it with our own
-                     * while keeping the application data.
-                     */
-                    logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath
-                            + " reverting from " + ps.codePathString + ": new version "
-                            + pkg.getLongVersionCode() + " better than installed "
-                            + ps.versionCode);
-                    InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
-                            ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
-                    synchronized (mInstallLock) {
-                        args.cleanUpResourcesLI();
-                    }
+                pkgSetting = null;
+            } else if (newPkgVersionGreater) {
+                // The application on /system is newer than the application on /data.
+                // Simply remove the application on /data [keeping application data]
+                // and replace it with the version on /system.
+                logCriticalInfo(Log.WARN,
+                        "System package enabled;"
+                        + " name: " + pkgSetting.name
+                        + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
+                        + "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
+                InstallArgs args = createInstallArgsForExisting(
+                        packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString,
+                        pkgSetting.resourcePathString, getAppDexInstructionSets(pkgSetting));
+                synchronized (mInstallLock) {
+                    args.cleanUpResourcesLI();
                 }
+            } else {
+                // The application on /system is older than the application on /data. Hide
+                // the application on /system and the version on /data will be scanned later
+                // and re-added like an update.
+                shouldHideSystemApp = true;
+                logCriticalInfo(Log.INFO,
+                        "System package disabled;"
+                        + " name: " + pkgSetting.name
+                        + "; old: " + pkgSetting.codePathString + " @ " + pkgSetting.versionCode
+                        + "; new: " + pkg.codePath + " @ " + pkg.codePath);
             }
         }
 
-        // The apk is forward locked (not public) if its code and resources
-        // are kept in different files. (except for app in either system or
-        // vendor path).
-        // TODO grab this value from PackageSettings
-        if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
-            if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
-                parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
-            }
-        }
-
-        final int userId = ((user == null) ? 0 : user.getIdentifier());
-        if (ps != null && ps.getInstantApp(userId)) {
-            scanFlags |= SCAN_AS_INSTANT_APP;
-        }
-        if (ps != null && ps.getVirtulalPreload(userId)) {
-            scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
-        }
-
-        // Note that we invoke the following method only if we are about to unpack an application
-        PackageParser.Package scannedPkg = scanPackageNewLI(pkg, parseFlags, scanFlags
+        final PackageParser.Package scannedPkg = scanPackageNewLI(pkg, parseFlags, scanFlags
                 | SCAN_UPDATE_SIGNATURE, currentTime, user);
 
-        /*
-         * If the system app should be overridden by a previously installed
-         * data, hide the system app now and let the /data/app scan pick it up
-         * again.
-         */
         if (shouldHideSystemApp) {
             synchronized (mPackages) {
                 mSettings.disableSystemPackageLPw(pkg.packageName, true);
             }
         }
-
         return scannedPkg;
     }
 
@@ -9518,9 +9410,10 @@
                     final String[] expectedCertDigests = requiredCertDigests[i];
                     // For apps targeting O MR1 we require explicit enumeration of all certs.
                     final String[] libCertDigests = (targetSdk > Build.VERSION_CODES.O)
-                            ? PackageUtils.computeSignaturesSha256Digests(libPkg.mSignatures)
+                            ? PackageUtils.computeSignaturesSha256Digests(
+                            libPkg.mSigningDetails.signatures)
                             : PackageUtils.computeSignaturesSha256Digests(
-                                    new Signature[]{libPkg.mSignatures[0]});
+                                    new Signature[]{libPkg.mSigningDetails.signatures[0]});
 
                     // Take a shortcut if sizes don't match. Note that if an app doesn't
                     // target O we don't parse the "additional-certificate" tags similarly
@@ -9717,9 +9610,52 @@
         }
     }
 
+    /**
+     * Returns the actual scan flags depending upon the state of the other settings.
+     * <p>Updated system applications will not have the following flags set
+     * by default and need to be adjusted after the fact:
+     * <ul>
+     * <li>{@link #SCAN_AS_SYSTEM}</li>
+     * <li>{@link #SCAN_AS_PRIVILEGED}</li>
+     * <li>{@link #SCAN_AS_OEM}</li>
+     * <li>{@link #SCAN_AS_VENDOR}</li>
+     * <li>{@link #SCAN_AS_INSTANT_APP}</li>
+     * <li>{@link #SCAN_AS_VIRTUAL_PRELOAD}</li>
+     * </ul>
+     */
+    private static @ScanFlags int adjustScanFlags(@ScanFlags int scanFlags,
+            PackageSetting pkgSetting, PackageSetting disabledPkgSetting, UserHandle user) {
+        if (disabledPkgSetting != null) {
+            // updated system application, must at least have SCAN_AS_SYSTEM
+            scanFlags |= SCAN_AS_SYSTEM;
+            if ((disabledPkgSetting.pkgPrivateFlags
+                    & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+                scanFlags |= SCAN_AS_PRIVILEGED;
+            }
+            if ((disabledPkgSetting.pkgPrivateFlags
+                    & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
+                scanFlags |= SCAN_AS_OEM;
+            }
+            if ((disabledPkgSetting.pkgPrivateFlags
+                    & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0) {
+                scanFlags |= SCAN_AS_VENDOR;
+            }
+        }
+        if (pkgSetting != null) {
+            final int userId = ((user == null) ? 0 : user.getIdentifier());
+            if (pkgSetting.getInstantApp(userId)) {
+                scanFlags |= SCAN_AS_INSTANT_APP;
+            }
+            if (pkgSetting.getVirtulalPreload(userId)) {
+                scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
+            }
+        }
+        return scanFlags;
+    }
+
     @GuardedBy("mInstallLock")
     private PackageParser.Package scanPackageNewLI(@NonNull PackageParser.Package pkg,
-            final @ParseFlags int parseFlags, final @ScanFlags int scanFlags, long currentTime,
+            final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
             @Nullable UserHandle user) throws PackageManagerException {
 
         final String renamedPkgName = mSettings.getRenamedPackageLPr(pkg.mRealPackage);
@@ -9737,6 +9673,7 @@
                     + " was transferred to another, but its .apk remains");
         }
 
+        scanFlags = adjustScanFlags(scanFlags, pkgSetting, disabledPkgSetting, user);
         synchronized (mPackages) {
             applyPolicy(pkg, parseFlags, scanFlags);
             assertPackageIsValid(pkg, parseFlags, scanFlags);
@@ -9791,6 +9728,7 @@
         final @ScanFlags int scanFlags = request.scanFlags;
         final PackageSetting oldPkgSetting = request.oldPkgSetting;
         final PackageSetting originalPkgSetting = request.originalPkgSetting;
+        final PackageSetting disabledPkgSetting = request.disabledPkgSetting;
         final UserHandle user = request.user;
         final String realPkgName = request.realPkgName;
         final PackageSetting pkgSetting = result.pkgSetting;
@@ -9856,14 +9794,14 @@
             if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, pkg)) {
                 // We just determined the app is signed correctly, so bring
                 // over the latest parsed certs.
-                pkgSetting.signatures.mSignatures = pkg.mSignatures;
+                pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures;
             } else {
                 if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
                     throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                             "Package " + pkg.packageName + " upgrade keys do not match the "
                                     + "previously installed version");
                 } else {
-                    pkgSetting.signatures.mSignatures = pkg.mSignatures;
+                    pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures;
                     String msg = "System package " + pkg.packageName
                             + " signature changed; retaining data.";
                     reportSettingsProblem(Log.WARN, msg);
@@ -9873,8 +9811,8 @@
             try {
                 final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
                 final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
-                final boolean compatMatch = verifySignatures(signatureCheckPs, pkg.mSignatures,
-                        compareCompat, compareRecover);
+                final boolean compatMatch = verifySignatures(signatureCheckPs, disabledPkgSetting,
+                        pkg.mSigningDetails, compareCompat, compareRecover);
                 // The new KeySets will be re-added later in the scanning process.
                 if (compatMatch) {
                     synchronized (mPackages) {
@@ -9883,14 +9821,14 @@
                 }
                 // We just determined the app is signed correctly, so bring
                 // over the latest parsed certs.
-                pkgSetting.signatures.mSignatures = pkg.mSignatures;
+                pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures;
             } catch (PackageManagerException e) {
                 if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
                     throw e;
                 }
                 // The signature has changed, but this package is in the system
                 // image...  let's recover!
-                pkgSetting.signatures.mSignatures = pkg.mSignatures;
+                pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures;
                 // However...  if this package is part of a shared user, but it
                 // doesn't match the signature of the shared user, let's fail.
                 // What this means is that you can't change the signatures
@@ -9898,7 +9836,7 @@
                 // that unreasonable.
                 if (signatureCheckPs.sharedUser != null) {
                     if (compareSignatures(signatureCheckPs.sharedUser.signatures.mSignatures,
-                            pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
+                            pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH) {
                         throw new PackageManagerException(
                                 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
                                 "Signature mismatch for shared user: "
@@ -9963,10 +9901,16 @@
      */
     private static @Nullable String getRealPackageName(@NonNull PackageParser.Package pkg,
             @Nullable String renamedPkgName) {
-        if (pkg.mOriginalPackages == null || !pkg.mOriginalPackages.contains(renamedPkgName)) {
-            return null;
+        if (isPackageRenamed(pkg, renamedPkgName)) {
+            return pkg.mRealPackage;
         }
-        return pkg.mRealPackage;
+        return null;
+    }
+
+    /** Returns {@code true} if the package has been renamed. Otherwise, {@code false}. */
+    private static boolean isPackageRenamed(@NonNull PackageParser.Package pkg,
+            @Nullable String renamedPkgName) {
+        return pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(renamedPkgName);
     }
 
     /**
@@ -9979,7 +9923,7 @@
     @GuardedBy("mPackages")
     private @Nullable PackageSetting getOriginalPackageLocked(@NonNull PackageParser.Package pkg,
             @Nullable String renamedPkgName) {
-        if (pkg.mOriginalPackages == null || pkg.mOriginalPackages.contains(renamedPkgName)) {
+        if (!isPackageRenamed(pkg, renamedPkgName)) {
             return null;
         }
         for (int i = pkg.mOriginalPackages.size() - 1; i >= 0; --i) {
@@ -10102,7 +10046,6 @@
             usesStaticLibraries = new String[pkg.usesStaticLibraries.size()];
             pkg.usesStaticLibraries.toArray(usesStaticLibraries);
         }
-
         final boolean createNewPackage = (pkgSetting == null);
         if (createNewPackage) {
             final String parentPackageName = (pkg.parentPackage != null)
@@ -10126,17 +10069,17 @@
             // secondaryCpuAbi are not known at this point so we always update them
             // to null here, only to reset them at a later point.
             Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, sharedUserSetting,
-                    destCodeFile, pkg.applicationInfo.nativeLibraryDir,
+                    destCodeFile, destResourceFile, pkg.applicationInfo.nativeLibraryDir,
                     pkg.applicationInfo.primaryCpuAbi, pkg.applicationInfo.secondaryCpuAbi,
                     pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags,
                     pkg.getChildPackageNames(), UserManagerService.getInstance(),
                     usesStaticLibraries, pkg.usesStaticLibrariesVersions);
         }
         if (createNewPackage && originalPkgSetting != null) {
-            // If we are first transitioning from an original package,
-            // fix up the new package's name now.  We need to do this after
-            // looking up the package under its new name, so getPackageLP
-            // can take care of fiddling things correctly.
+            // This is the initial transition from the original package, so,
+            // fix up the new package's name now. We must do this after looking
+            // up the package under its new name, so getPackageLP takes care of
+            // fiddling things correctly.
             pkg.setPackageName(originalPkgSetting.name);
 
             // File a report about this.
@@ -10223,7 +10166,7 @@
         // would've already compiled the app without taking the package setting into
         // account.
         if ((scanFlags & SCAN_NO_DEX) == 0 && (scanFlags & SCAN_NEW_INSTALL) != 0) {
-            if (cpuAbiOverride == null && pkgSetting.cpuAbiOverrideString != null) {
+            if (cpuAbiOverride == null && pkg.packageName != null) {
                 Slog.w(TAG, "Ignoring persisted ABI override " + cpuAbiOverride +
                         " for package " + pkg.packageName);
             }
@@ -10238,7 +10181,7 @@
         pkg.cpuAbiOverride = cpuAbiOverride;
 
         if (DEBUG_ABI_SELECTION) {
-            Slog.d(TAG, "Resolved nativeLibraryRoot for " + pkg.applicationInfo.packageName
+            Slog.d(TAG, "Resolved nativeLibraryRoot for " + pkg.packageName
                     + " to root=" + pkg.applicationInfo.nativeLibraryRootDir + ", isa="
                     + pkg.applicationInfo.nativeLibraryRootRequiresIsa);
         }
@@ -10293,6 +10236,22 @@
         }
         pkgSetting.setTimeStamp(scanFileTime);
 
+        pkgSetting.pkg = pkg;
+        pkgSetting.pkgFlags = pkg.applicationInfo.flags;
+        if (pkg.getLongVersionCode() != pkgSetting.versionCode) {
+            pkgSetting.versionCode = pkg.getLongVersionCode();
+        }
+        // Update volume if needed
+        final String volumeUuid = pkg.applicationInfo.volumeUuid;
+        if (!Objects.equals(volumeUuid, pkgSetting.volumeUuid)) {
+            Slog.i(PackageManagerService.TAG,
+                    "Update" + (pkgSetting.isSystem() ? " system" : "")
+                    + " package " + pkg.packageName
+                    + " volume from " + pkgSetting.volumeUuid
+                    + " to " + volumeUuid);
+            pkgSetting.volumeUuid = volumeUuid;
+        }
+
         return new ScanResult(true, pkgSetting, changedAbiCodePath);
     }
 
@@ -13204,7 +13163,7 @@
     void installStage(String packageName, File stagedDir,
             IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
             String installerPackageName, int installerUid, UserHandle user,
-            Certificate[][] certificates) {
+            PackageParser.SigningDetails signingDetails) {
         if (DEBUG_EPHEMERAL) {
             if ((sessionParams.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
                 Slog.d(TAG, "Ephemeral install of " + packageName);
@@ -13222,7 +13181,7 @@
         final InstallParams params = new InstallParams(origin, null, observer,
                 sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
                 verificationInfo, user, sessionParams.abiOverride,
-                sessionParams.grantedRuntimePermissions, certificates, installReason);
+                sessionParams.grantedRuntimePermissions, signingDetails, installReason);
         params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
         msg.obj = params;
 
@@ -13826,7 +13785,7 @@
             final PackageParser.Package pkg = mPackages.get(verifierInfo.packageName);
             if (pkg == null) {
                 return -1;
-            } else if (pkg.mSignatures.length != 1) {
+            } else if (pkg.mSigningDetails.signatures.length != 1) {
                 Slog.i(TAG, "Verifier package " + verifierInfo.packageName
                         + " has more than one signature; ignoring");
                 return -1;
@@ -13840,7 +13799,7 @@
 
             final byte[] expectedPublicKey;
             try {
-                final Signature verifierSig = pkg.mSignatures[0];
+                final Signature verifierSig = pkg.mSigningDetails.signatures[0];
                 final PublicKey publicKey = verifierSig.getPublicKey();
                 expectedPublicKey = publicKey.getEncoded();
             } catch (CertificateException e) {
@@ -14532,13 +14491,13 @@
         final String packageAbiOverride;
         final String[] grantedRuntimePermissions;
         final VerificationInfo verificationInfo;
-        final Certificate[][] certificates;
+        final PackageParser.SigningDetails signingDetails;
         final int installReason;
 
         InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
                 int installFlags, String installerPackageName, String volumeUuid,
                 VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride,
-                String[] grantedPermissions, Certificate[][] certificates, int installReason) {
+                String[] grantedPermissions, PackageParser.SigningDetails signingDetails, int installReason) {
             super(user);
             this.origin = origin;
             this.move = move;
@@ -14549,7 +14508,7 @@
             this.verificationInfo = verificationInfo;
             this.packageAbiOverride = packageAbiOverride;
             this.grantedRuntimePermissions = grantedPermissions;
-            this.certificates = certificates;
+            this.signingDetails = signingDetails;
             this.installReason = installReason;
         }
 
@@ -14980,7 +14939,7 @@
         /** If non-null, drop an async trace when the install completes */
         final String traceMethod;
         final int traceCookie;
-        final Certificate[][] certificates;
+        final PackageParser.SigningDetails signingDetails;
         final int installReason;
 
         // The list of instruction sets supported by this app. This is currently
@@ -14992,7 +14951,7 @@
                 int installFlags, String installerPackageName, String volumeUuid,
                 UserHandle user, String[] instructionSets,
                 String abiOverride, String[] installGrantPermissions,
-                String traceMethod, int traceCookie, Certificate[][] certificates,
+                String traceMethod, int traceCookie, PackageParser.SigningDetails signingDetails,
                 int installReason) {
             this.origin = origin;
             this.move = move;
@@ -15006,7 +14965,7 @@
             this.installGrantPermissions = installGrantPermissions;
             this.traceMethod = traceMethod;
             this.traceCookie = traceCookie;
-            this.certificates = certificates;
+            this.signingDetails = signingDetails;
             this.installReason = installReason;
         }
 
@@ -15102,7 +15061,7 @@
                     params.installerPackageName, params.volumeUuid,
                     params.getUser(), null /*instructionSets*/, params.packageAbiOverride,
                     params.grantedRuntimePermissions,
-                    params.traceMethod, params.traceCookie, params.certificates,
+                    params.traceMethod, params.traceCookie, params.signingDetails,
                     params.installReason);
             if (isFwdLocked()) {
                 throw new IllegalArgumentException("Forward locking only supported in ASEC");
@@ -15112,7 +15071,7 @@
         /** Existing install */
         FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) {
             super(OriginInfo.fromNothing(), null, null, 0, null, null, null, instructionSets,
-                    null, null, null, 0, null /*certificates*/,
+                    null, null, null, 0, PackageParser.SigningDetails.UNKNOWN,
                     PackageManager.INSTALL_REASON_UNKNOWN);
             this.codeFile = (codePath != null) ? new File(codePath) : null;
             this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null;
@@ -15333,7 +15292,7 @@
                     params.installerPackageName, params.volumeUuid,
                     params.getUser(), null /* instruction sets */, params.packageAbiOverride,
                     params.grantedRuntimePermissions,
-                    params.traceMethod, params.traceCookie, params.certificates,
+                    params.traceMethod, params.traceCookie, params.signingDetails,
                     params.installReason);
         }
 
@@ -15672,7 +15631,8 @@
                 }
             } else {
                 // default to original signature matching
-                if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures)
+                if (compareSignatures(oldPackage.mSigningDetails.signatures,
+                        pkg.mSigningDetails.signatures)
                         != PackageManager.SIGNATURE_MATCH) {
                     res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                             "New package has a different signature: " + pkgName);
@@ -16384,7 +16344,6 @@
                 | PackageParser.PARSE_ENFORCE_CODE
                 | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
                 | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0)
-                | (instantApp ? PackageParser.PARSE_IS_EPHEMERAL : 0)
                 | (forceSdk ? PackageParser.PARSE_FORCE_SDK : 0);
         PackageParser pp = new PackageParser();
         pp.setSeparateProcesses(mSeparateProcesses);
@@ -16412,19 +16371,29 @@
             return;
         }
 
-        // Instant apps must have target SDK >= O and have targetSanboxVersion >= 2
-        if (instantApp && pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
-            Slog.w(TAG, "Instant app package " + pkg.packageName + " does not target O");
-            res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE,
-                    "Instant app package must target O");
-            return;
-        }
-        if (instantApp && pkg.applicationInfo.targetSandboxVersion != 2) {
-            Slog.w(TAG, "Instant app package " + pkg.packageName
-                    + " does not target targetSandboxVersion 2");
-            res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE,
-                    "Instant app package must use targetSanboxVersion 2");
-            return;
+        // Instant apps have several additional install-time checks.
+        if (instantApp) {
+            if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+                Slog.w(TAG,
+                        "Instant app package " + pkg.packageName + " does not target at least O");
+                res.setError(INSTALL_FAILED_INSTANT_APP_INVALID,
+                        "Instant app package must target at least O");
+                return;
+            }
+            if (pkg.applicationInfo.targetSandboxVersion != 2) {
+                Slog.w(TAG, "Instant app package " + pkg.packageName
+                        + " does not target targetSandboxVersion 2");
+                res.setError(INSTALL_FAILED_INSTANT_APP_INVALID,
+                        "Instant app package must use targetSandboxVersion 2");
+                return;
+            }
+            if (pkg.mSharedUserId != null) {
+                Slog.w(TAG, "Instant app package " + pkg.packageName
+                        + " may not declare sharedUserId.");
+                res.setError(INSTALL_FAILED_INSTANT_APP_INVALID,
+                        "Instant app package may not declare a sharedUserId");
+                return;
+            }
         }
 
         if (pkg.applicationInfo.isStaticSharedLibrary()) {
@@ -16484,14 +16453,8 @@
 
         try {
             // either use what we've been given or parse directly from the APK
-            if (args.certificates != null) {
-                try {
-                    PackageParser.populateCertificates(pkg, args.certificates);
-                } catch (PackageParserException e) {
-                    // there was something wrong with the certificates we were given;
-                    // try to pull them from the APK
-                    PackageParser.collectCertificates(pkg, parseFlags);
-                }
+            if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) {
+                pkg.setSigningDetails(args.signingDetails);
             } else {
                 PackageParser.collectCertificates(pkg, parseFlags);
             }
@@ -16500,6 +16463,15 @@
             return;
         }
 
+        if (instantApp && pkg.mSigningDetails.signatureSchemeVersion
+                < SignatureSchemeVersion.SIGNING_BLOCK_V2) {
+            Slog.w(TAG, "Instant app package " + pkg.packageName
+                    + " is not signed with at least APK Signature Scheme v2");
+            res.setError(INSTALL_FAILED_INSTANT_APP_INVALID,
+                    "Instant app package must be signed with APK Signature Scheme v2 or greater");
+            return;
+        }
+
         // Get rid of all references to package scan path via parser.
         pp = null;
         String oldCodePath = null;
@@ -16609,8 +16581,10 @@
                     try {
                         final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
                         final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
+                        // We don't care about disabledPkgSetting on install for now.
                         final boolean compatMatch = verifySignatures(
-                                signatureCheckPs, pkg.mSignatures, compareCompat, compareRecover);
+                                signatureCheckPs, null, pkg.mSigningDetails, compareCompat,
+                                compareRecover);
                         // The new KeySets will be re-added later in the scanning process.
                         if (compatMatch) {
                             synchronized (mPackages) {
@@ -16661,7 +16635,7 @@
                         sigsOk = ksms.checkUpgradeKeySetLocked(sourcePackageSetting, pkg);
                     } else {
                         sigsOk = compareSignatures(sourcePackageSetting.signatures.mSignatures,
-                                pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
+                                pkg.mSigningDetails.signatures) == PackageManager.SIGNATURE_MATCH;
                     }
                     if (!sigsOk) {
                         // If the owning package is the system itself, we log but allow
@@ -16937,7 +16911,8 @@
                 for (ActivityIntentInfo filter : a.intents) {
                     if (filter.needsVerification() && needsNetworkVerificationLPr(filter)) {
                         if (DEBUG_DOMAIN_VERIFICATION) {
-                            Slog.d(TAG, "Intent filter needs verification, so processing all filters");
+                            Slog.d(TAG,
+                                    "Intent filter needs verification, so processing all filters");
                         }
                         needToVerify = true;
                         break;
@@ -18267,7 +18242,8 @@
                     null /*enabledComponents*/,
                     null /*disabledComponents*/,
                     ps.readUserState(nextUserId).domainVerificationStatus,
-                    0, PackageManager.INSTALL_REASON_UNKNOWN);
+                    0, PackageManager.INSTALL_REASON_UNKNOWN,
+                    null /*harmfulAppWarning*/);
         }
         mSettings.writeKernelMappingLPr(ps);
     }
@@ -22245,8 +22221,8 @@
         final OriginInfo origin = OriginInfo.fromExistingFile(codeFile);
         final InstallParams params = new InstallParams(origin, move, installObserver, installFlags,
                 installerPackageName, volumeUuid, null /*verificationInfo*/, user,
-                packageAbiOverride, null /*grantedPermissions*/, null /*certificates*/,
-                PackageManager.INSTALL_REASON_UNKNOWN);
+                packageAbiOverride, null /*grantedPermissions*/,
+                PackageParser.SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN);
         params.setTraceMethod("movePackage").setTraceCookie(System.identityHashCode(params));
         msg.obj = params;
 
@@ -23579,6 +23555,47 @@
         }
         return unusedPackages;
     }
+
+    @Override
+    public void setHarmfulAppWarning(@NonNull String packageName, @Nullable CharSequence warning,
+            int userId) {
+        final int callingUid = Binder.getCallingUid();
+        final int callingAppId = UserHandle.getAppId(callingUid);
+
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
+                true /*requireFullPermission*/, true /*checkShell*/, "setHarmfulAppInfo");
+
+        if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID &&
+                checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid) != PERMISSION_GRANTED) {
+            throw new SecurityException("Caller must have the "
+                    + SET_HARMFUL_APP_WARNINGS + " permission.");
+        }
+
+        synchronized(mPackages) {
+            mSettings.setHarmfulAppWarningLPw(packageName, warning, userId);
+            scheduleWritePackageRestrictionsLocked(userId);
+        }
+    }
+
+    @Nullable
+    @Override
+    public CharSequence getHarmfulAppWarning(@NonNull String packageName, int userId) {
+        final int callingUid = Binder.getCallingUid();
+        final int callingAppId = UserHandle.getAppId(callingUid);
+
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
+                true /*requireFullPermission*/, true /*checkShell*/, "getHarmfulAppInfo");
+
+        if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID &&
+                checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid) != PERMISSION_GRANTED) {
+            throw new SecurityException("Caller must have the "
+                    + SET_HARMFUL_APP_WARNINGS + " permission.");
+        }
+
+        synchronized(mPackages) {
+            return mSettings.getHarmfulAppWarningLPr(packageName, userId);
+        }
+    }
 }
 
 interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 20ec9b5..7b96ca6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -33,10 +33,12 @@
 import com.android.server.pm.dex.PackageDexUsage;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
 import android.os.Build;
@@ -45,6 +47,7 @@
 import android.os.FileUtils;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.service.pm.PackageServiceDumpProto;
 import android.system.ErrnoException;
@@ -504,13 +507,13 @@
      * system upgrade) and {@code scannedSigs} will be in the newer format.
      */
     private static boolean matchSignaturesCompat(String packageName,
-            PackageSignatures packageSignatures, Signature[] parsedSignatures) {
+            PackageSignatures packageSignatures, PackageParser.SigningDetails parsedSignatures) {
         ArraySet<Signature> existingSet = new ArraySet<Signature>();
         for (Signature sig : packageSignatures.mSignatures) {
             existingSet.add(sig);
         }
         ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
-        for (Signature sig : parsedSignatures) {
+        for (Signature sig : parsedSignatures.signatures) {
             try {
                 Signature[] chainSignatures = sig.getChainSignatures();
                 for (Signature chainSig : chainSignatures) {
@@ -547,27 +550,69 @@
     }
 
     /**
+     * Make sure the updated priv app is signed with the same key as the original APK file on the
+     * /system partition.
+     *
+     * <p>The rationale is that {@code disabledPkg} is a PackageSetting backed by xml files in /data
+     * and is not tamperproof.
+     */
+    private static boolean matchSignatureInSystem(PackageSetting pkgSetting,
+            PackageSetting disabledPkgSetting) {
+        try {
+            PackageParser.collectCertificates(disabledPkgSetting.pkg,
+                    PackageParser.PARSE_IS_SYSTEM_DIR);
+            if (compareSignatures(pkgSetting.signatures.mSignatures,
+                        disabledPkgSetting.signatures.mSignatures)
+                    != PackageManager.SIGNATURE_MATCH) {
+                logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " +
+                        pkgSetting.name);
+                return false;
+            }
+        } catch (PackageParserException e) {
+            logCriticalInfo(Log.ERROR, "Failed to collect cert for " + pkgSetting.name + ": " +
+                    e.getMessage());
+            return false;
+        }
+        return true;
+    }
+
+    /** Returns true to force apk verification if the updated package (in /data) is a priv app. */
+    static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) {
+        return disabledPs != null && disabledPs.isPrivileged() &&
+                SystemProperties.getInt("ro.apk_verity.mode", 0) != 0;
+    }
+
+    /**
      * Verifies that signatures match.
      * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
      * @throws PackageManagerException if the signatures did not match.
      */
     public static boolean verifySignatures(PackageSetting pkgSetting,
-            Signature[] parsedSignatures, boolean compareCompat, boolean compareRecover)
+            PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
+            boolean compareCompat, boolean compareRecover)
             throws PackageManagerException {
         final String packageName = pkgSetting.name;
         boolean compatMatch = false;
         if (pkgSetting.signatures.mSignatures != null) {
             // Already existing package. Make sure signatures match
-            boolean match = compareSignatures(pkgSetting.signatures.mSignatures, parsedSignatures)
+            boolean match = compareSignatures(pkgSetting.signatures.mSignatures,
+                    parsedSignatures.signatures)
                     == PackageManager.SIGNATURE_MATCH;
             if (!match && compareCompat) {
-                match = matchSignaturesCompat(packageName, pkgSetting.signatures, parsedSignatures);
+                match = matchSignaturesCompat(packageName, pkgSetting.signatures,
+                        parsedSignatures);
                 compatMatch = match;
             }
             if (!match && compareRecover) {
                 match = matchSignaturesRecover(
-                        packageName, pkgSetting.signatures.mSignatures, parsedSignatures);
+                        packageName, pkgSetting.signatures.mSignatures,
+                        parsedSignatures.signatures);
             }
+
+            if (!match && isApkVerificationForced(disabledPkgSetting)) {
+                match = matchSignatureInSystem(pkgSetting, disabledPkgSetting);
+            }
+
             if (!match) {
                 throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                         "Package " + packageName +
@@ -578,14 +623,14 @@
         if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
             // Already existing package. Make sure signatures match
             boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
-                    parsedSignatures) == PackageManager.SIGNATURE_MATCH;
+                    parsedSignatures.signatures) == PackageManager.SIGNATURE_MATCH;
             if (!match && compareCompat) {
                 match = matchSignaturesCompat(
                         packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
             }
             if (!match && compareRecover) {
-                match = matchSignaturesRecover(
-                        packageName, pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures);
+                match = matchSignaturesRecover(packageName,
+                        pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures.signatures);
                 compatMatch |= match;
             }
             if (!match) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 2d82c46..bd1cd33 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -230,6 +230,8 @@
                     return runGetInstantAppResolver();
                 case "has-feature":
                     return runHasFeature();
+                case "set-harmful-app-warning":
+                    return runSetHarmfulAppWarning();
                 default: {
                     String nextArg = getNextArg();
                     if (nextArg == null) {
@@ -1277,7 +1279,7 @@
             return runRemoveSplit(packageName, splitName);
         }
 
-        userId = translateUserId(userId, "runUninstall");
+        userId = translateUserId(userId, true /*allowAll*/, "runUninstall");
         if (userId == UserHandle.USER_ALL) {
             userId = UserHandle.USER_SYSTEM;
             flags |= PackageManager.DELETE_ALL_USERS;
@@ -2088,6 +2090,29 @@
         return 0;
     }
 
+    private int runSetHarmfulAppWarning() throws RemoteException {
+        int userId = UserHandle.USER_CURRENT;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+
+        userId = translateUserId(userId, false /*allowAll*/, "runSetHarmfulAppWarning");
+
+        final String packageName = getNextArgRequired();
+        final String warning = getNextArg();
+
+        mInterface.setHarmfulAppWarning(packageName, warning, userId);
+
+        return 0;
+    }
+
     private static String checkAbiArgument(String abi) {
         if (TextUtils.isEmpty(abi)) {
             throw new IllegalArgumentException("Missing ABI argument");
@@ -2107,14 +2132,14 @@
         throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
     }
 
-    private int translateUserId(int userId, String logContext) {
+    private int translateUserId(int userId, boolean allowAll, String logContext) {
         return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, true, true, logContext, "pm command");
+                userId, allowAll, true, logContext, "pm command");
     }
 
     private int doCreateSession(SessionParams params, String installerPackageName, int userId)
             throws RemoteException {
-        userId = translateUserId(userId, "runInstallCreate");
+        userId = translateUserId(userId, true /*allowAll*/, "runInstallCreate");
         if (userId == UserHandle.USER_ALL) {
             userId = UserHandle.USER_SYSTEM;
             params.installFlags |= PackageManager.INSTALL_ALL_USERS;
@@ -2634,6 +2659,9 @@
         pw.println("");
         pw.println("  get-instantapp-resolver");
         pw.println("    Return the name of the component that is the current instant app installer.");
+        pw.println("");
+        pw.println("  set-harmful-app-warning [--user <USER_ID>] <PACKAGE> [<WARNING>]");
+        pw.println("    Mark the app as harmful with the given warning message.");
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
     }
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 809e16c..e3c4c43 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -437,7 +437,8 @@
             boolean notLaunched, boolean hidden, boolean suspended, boolean instantApp,
             boolean virtualPreload, String lastDisableAppCaller,
             ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
-            int domainVerifState, int linkGeneration, int installReason) {
+            int domainVerifState, int linkGeneration, int installReason,
+            String harmfulAppWarning) {
         PackageUserState state = modifyUserState(userId);
         state.ceDataInode = ceDataInode;
         state.enabled = enabled;
@@ -454,6 +455,7 @@
         state.installReason = installReason;
         state.instantApp = instantApp;
         state.virtualPreload = virtualPreload;
+        state.harmfulAppWarning = harmfulAppWarning;
     }
 
     ArraySet<String> getEnabledComponents(int userId) {
@@ -620,4 +622,14 @@
             proto.end(userToken);
         }
     }
+
+    void setHarmfulAppWarning(int userId, String harmfulAppWarning) {
+        PackageUserState userState = modifyUserState(userId);
+        userState.harmfulAppWarning = harmfulAppWarning;
+    }
+
+    String getHarmfulAppWarning(int userId) {
+        PackageUserState userState = readUserState(userId);
+        return userState.harmfulAppWarning;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageSignatures.java b/services/core/java/com/android/server/pm/PackageSignatures.java
index f5c81e4..d567d5c 100644
--- a/services/core/java/com/android/server/pm/PackageSignatures.java
+++ b/services/core/java/com/android/server/pm/PackageSignatures.java
@@ -22,6 +22,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.Signature;
 import android.util.Log;
 
@@ -30,15 +32,17 @@
 
 class PackageSignatures {
     Signature[] mSignatures;
+    @SignatureSchemeVersion int mSignatureSchemeVersion;
 
     PackageSignatures(PackageSignatures orig) {
         if (orig != null && orig.mSignatures != null) {
             mSignatures = orig.mSignatures.clone();
+            mSignatureSchemeVersion = orig.mSignatureSchemeVersion;
         }
     }
 
-    PackageSignatures(Signature[] sigs) {
-        assignSignatures(sigs);
+    PackageSignatures(PackageParser.SigningDetails signingDetails) {
+        assignSignatures(signingDetails);
     }
 
     PackageSignatures() {
@@ -52,6 +56,7 @@
         serializer.startTag(null, tagName);
         serializer.attribute(null, "count",
                 Integer.toString(mSignatures.length));
+        serializer.attribute(null, "schemeVersion", Integer.toString(mSignatureSchemeVersion));
         for (int i=0; i<mSignatures.length; i++) {
             serializer.startTag(null, "cert");
             final Signature sig = mSignatures[i];
@@ -84,6 +89,15 @@
                        + " no count at " + parser.getPositionDescription());
             XmlUtils.skipCurrentTag(parser);
         }
+        String schemeVersionStr = parser.getAttributeValue(null, "schemeVersion");
+        if (schemeVersionStr == null) {
+            PackageManagerService.reportSettingsProblem(Log.WARN,
+                    "Error in package manager settings: <signatures> has no schemeVersion at "
+                        + parser.getPositionDescription());
+            mSignatureSchemeVersion = SignatureSchemeVersion.UNKNOWN;
+        } else {
+            mSignatureSchemeVersion = Integer.parseInt(countStr);
+        }
         final int count = Integer.parseInt(countStr);
         mSignatures = new Signature[count];
         int pos = 0;
@@ -174,14 +188,15 @@
         }
     }
 
-    void assignSignatures(Signature[] sigs) {
-        if (sigs == null) {
+    void assignSignatures(PackageParser.SigningDetails signingDetails) {
+        mSignatureSchemeVersion = signingDetails.signatureSchemeVersion;
+        if (!signingDetails.hasSignatures()) {
             mSignatures = null;
             return;
         }
-        mSignatures = new Signature[sigs.length];
-        for (int i=0; i<sigs.length; i++) {
-            mSignatures[i] = sigs[i];
+        mSignatures = new Signature[signingDetails.signatures.length];
+        for (int i=0; i<signingDetails.signatures.length; i++) {
+            mSignatures[i] = signingDetails.signatures[i];
         }
     }
 
@@ -190,7 +205,9 @@
         StringBuffer buf = new StringBuffer(128);
         buf.append("PackageSignatures{");
         buf.append(Integer.toHexString(System.identityHashCode(this)));
-        buf.append(" [");
+        buf.append(" version:");
+        buf.append(mSignatureSchemeVersion);
+        buf.append(", signatures:[");
         if (mSignatures != null) {
             for (int i=0; i<mSignatures.length; i++) {
                 if (i > 0) buf.append(", ");
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index fbf3d82..2552643 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -17,9 +17,8 @@
 package com.android.server.pm;
 
 import android.content.pm.PackageParser;
-import android.content.pm.PackageUserState;
-import android.content.pm.SELinuxUtil;
 import android.content.pm.Signature;
+import android.content.pm.PackageParser.SigningDetails;
 import android.os.Environment;
 import android.util.Slog;
 import android.util.Xml;
@@ -453,7 +452,8 @@
     public String getMatchedSeInfo(PackageParser.Package pkg) {
         // Check for exact signature matches across all certs.
         Signature[] certs = mCerts.toArray(new Signature[0]);
-        if (!Signature.areExactMatch(certs, pkg.mSignatures)) {
+        if (pkg.mSigningDetails != SigningDetails.UNKNOWN
+                && !Signature.areExactMatch(certs, pkg.mSigningDetails.signatures)) {
             return null;
         }
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4cf1814..ecbc452 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -227,6 +227,7 @@
     private static final String ATTR_INSTALL_REASON = "install-reason";
     private static final String ATTR_INSTANT_APP = "instant-app";
     private static final String ATTR_VIRTUAL_PRELOAD = "virtual-preload";
+    private static final String ATTR_HARMFUL_APP_WARNING = "harmful-app-warning";
 
     private static final String ATTR_PACKAGE_NAME = "packageName";
     private static final String ATTR_FINGERPRINT = "fingerprint";
@@ -742,7 +743,8 @@
                                 null /*enabledComponents*/,
                                 null /*disabledComponents*/,
                                 INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED,
-                                0, PackageManager.INSTALL_REASON_UNKNOWN);
+                                0, PackageManager.INSTALL_REASON_UNKNOWN,
+                                null /*harmfulAppWarning*/);
                     }
                 }
             }
@@ -783,11 +785,12 @@
      */
     static void updatePackageSetting(@NonNull PackageSetting pkgSetting,
             @Nullable PackageSetting disabledPkg, @Nullable SharedUserSetting sharedUser,
-            @NonNull File codePath, @Nullable String legacyNativeLibraryPath,
-            @Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi,
-            int pkgFlags, int pkgPrivateFlags, @Nullable List<String> childPkgNames,
-            @NonNull UserManagerService userManager, @Nullable String[] usesStaticLibraries,
-            @Nullable long[] usesStaticLibrariesVersions) throws PackageManagerException {
+            @NonNull File codePath, File resourcePath,
+            @Nullable String legacyNativeLibraryPath, @Nullable String primaryCpuAbi,
+            @Nullable String secondaryCpuAbi, int pkgFlags, int pkgPrivateFlags,
+            @Nullable List<String> childPkgNames, @NonNull UserManagerService userManager,
+            @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions)
+                    throws PackageManagerException {
         final String pkgName = pkgSetting.name;
         if (pkgSetting.sharedUser != sharedUser) {
             PackageManagerService.reportSettingsProblem(Log.WARN,
@@ -799,29 +802,19 @@
         }
 
         if (!pkgSetting.codePath.equals(codePath)) {
-            // Check to see if its a disabled system app
-            if ((pkgSetting.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-                // This is an updated system app with versions in both system
-                // and data partition. Just let the most recent version
-                // take precedence.
-                Slog.w(PackageManagerService.TAG,
-                        "Trying to update system app code path from "
-                        + pkgSetting.codePathString + " to " + codePath.toString());
-            } else {
-                // Just a change in the code path is not an issue, but
-                // let's log a message about it.
-                Slog.i(PackageManagerService.TAG,
-                        "Package " + pkgName + " codePath changed from "
-                        + pkgSetting.codePath + " to " + codePath
-                        + "; Retaining data and using new");
-
-                // The owner user's installed flag is set false
-                // when the application was installed by other user
-                // and the installed flag is not updated
-                // when the application is appended as system app later.
-                if ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0
-                        && disabledPkg == null) {
-                    List<UserInfo> allUserInfos = getAllUsers(userManager);
+            final boolean isSystem = pkgSetting.isSystem();
+            Slog.i(PackageManagerService.TAG,
+                    "Update" + (isSystem ? " system" : "")
+                    + " package " + pkgName
+                    + " code path from " + pkgSetting.codePathString
+                    + " to " + codePath.toString()
+                    + "; Retain data and using new");
+            if (!isSystem) {
+                // The package isn't considered as installed if the application was
+                // first installed by another user. Update the installed flag when the
+                // application ever becomes part of the system.
+                if ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0 && disabledPkg == null) {
+                    final List<UserInfo> allUserInfos = getAllUsers(userManager);
                     if (allUserInfos != null) {
                         for (UserInfo userInfo : allUserInfos) {
                             pkgSetting.setInstalled(true, userInfo.id);
@@ -829,14 +822,24 @@
                     }
                 }
 
-                /*
-                 * Since we've changed paths, we need to prefer the new
-                 * native library path over the one stored in the
-                 * package settings since we might have moved from
-                 * internal to external storage or vice versa.
-                 */
+                // Since we've changed paths, prefer the new native library path over
+                // the one stored in the package settings since we might have moved from
+                // internal to external storage or vice versa.
                 pkgSetting.legacyNativeLibraryPathString = legacyNativeLibraryPath;
             }
+            pkgSetting.codePath = codePath;
+            pkgSetting.codePathString = codePath.toString();
+        }
+        if (!pkgSetting.resourcePath.equals(resourcePath)) {
+            final boolean isSystem = pkgSetting.isSystem();
+            Slog.i(PackageManagerService.TAG,
+                    "Update" + (isSystem ? " system" : "")
+                    + " package " + pkgName
+                    + " resource path from " + pkgSetting.resourcePathString
+                    + " to " + resourcePath.toString()
+                    + "; Retain data and using new");
+            pkgSetting.resourcePath = resourcePath;
+            pkgSetting.resourcePathString = resourcePath.toString();
         }
         // If what we are scanning is a system (and possibly privileged) package,
         // then make it so, regardless of whether it was previously installed only
@@ -853,13 +856,14 @@
         if (childPkgNames != null) {
             pkgSetting.childPackageNames = new ArrayList<>(childPkgNames);
         }
-        if (usesStaticLibraries != null) {
-            pkgSetting.usesStaticLibraries = Arrays.copyOf(usesStaticLibraries,
-                    usesStaticLibraries.length);
-        }
-        if (usesStaticLibrariesVersions != null) {
-            pkgSetting.usesStaticLibrariesVersions = Arrays.copyOf(usesStaticLibrariesVersions,
-                    usesStaticLibrariesVersions.length);
+        // Update static shared library dependencies if needed
+        if (usesStaticLibraries != null && usesStaticLibrariesVersions != null
+                && usesStaticLibraries.length == usesStaticLibrariesVersions.length) {
+            pkgSetting.usesStaticLibraries = usesStaticLibraries;
+            pkgSetting.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
+        } else {
+            pkgSetting.usesStaticLibraries = null;
+            pkgSetting.usesStaticLibrariesVersions = null;
         }
     }
 
@@ -912,69 +916,17 @@
                 userId);
     }
 
+    // TODO: Move to scanPackageOnlyLI() after verifying signatures are setup correctly
+    // by that time.
     void insertPackageSettingLPw(PackageSetting p, PackageParser.Package pkg) {
-        p.pkg = pkg;
-        // pkg.mSetEnabled = p.getEnabled(userId);
-        // pkg.mSetStopped = p.getStopped(userId);
-        final String volumeUuid = pkg.applicationInfo.volumeUuid;
-        final String codePath = pkg.applicationInfo.getCodePath();
-        final String resourcePath = pkg.applicationInfo.getResourcePath();
-        final String legacyNativeLibraryPath = pkg.applicationInfo.nativeLibraryRootDir;
-        // Update volume if needed
-        if (!Objects.equals(volumeUuid, p.volumeUuid)) {
-            Slog.w(PackageManagerService.TAG, "Volume for " + p.pkg.packageName +
-                    " changing from " + p.volumeUuid + " to " + volumeUuid);
-            p.volumeUuid = volumeUuid;
-        }
-        // Update code path if needed
-        if (!Objects.equals(codePath, p.codePathString)) {
-            Slog.w(PackageManagerService.TAG, "Code path for " + p.pkg.packageName +
-                    " changing from " + p.codePathString + " to " + codePath);
-            p.codePath = new File(codePath);
-            p.codePathString = codePath;
-        }
-        //Update resource path if needed
-        if (!Objects.equals(resourcePath, p.resourcePathString)) {
-            Slog.w(PackageManagerService.TAG, "Resource path for " + p.pkg.packageName +
-                    " changing from " + p.resourcePathString + " to " + resourcePath);
-            p.resourcePath = new File(resourcePath);
-            p.resourcePathString = resourcePath;
-        }
-        // Update the native library paths if needed
-        if (!Objects.equals(legacyNativeLibraryPath, p.legacyNativeLibraryPathString)) {
-            p.legacyNativeLibraryPathString = legacyNativeLibraryPath;
-        }
-
-        // Update the required Cpu Abi
-        p.primaryCpuAbiString = pkg.applicationInfo.primaryCpuAbi;
-        p.secondaryCpuAbiString = pkg.applicationInfo.secondaryCpuAbi;
-        p.cpuAbiOverrideString = pkg.cpuAbiOverride;
-        // Update version code if needed
-        if (pkg.getLongVersionCode() != p.versionCode) {
-            p.versionCode = pkg.getLongVersionCode();
-        }
         // Update signatures if needed.
         if (p.signatures.mSignatures == null) {
-            p.signatures.assignSignatures(pkg.mSignatures);
-        }
-        // Update flags if needed.
-        if (pkg.applicationInfo.flags != p.pkgFlags) {
-            p.pkgFlags = pkg.applicationInfo.flags;
+            p.signatures.assignSignatures(pkg.mSigningDetails);
         }
         // If this app defines a shared user id initialize
         // the shared user signatures as well.
         if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) {
-            p.sharedUser.signatures.assignSignatures(pkg.mSignatures);
-        }
-        // Update static shared library dependencies if needed
-        if (pkg.usesStaticLibraries != null && pkg.usesStaticLibrariesVersions != null
-                && pkg.usesStaticLibraries.size() == pkg.usesStaticLibrariesVersions.length) {
-            p.usesStaticLibraries = new String[pkg.usesStaticLibraries.size()];
-            pkg.usesStaticLibraries.toArray(p.usesStaticLibraries);
-            p.usesStaticLibrariesVersions = pkg.usesStaticLibrariesVersions;
-        } else {
-            p.usesStaticLibraries = null;
-            p.usesStaticLibrariesVersions = null;
+            p.sharedUser.signatures.assignSignatures(pkg.mSigningDetails);
         }
         addPackageSettingLPw(p, p.sharedUser);
     }
@@ -1680,7 +1632,8 @@
                                 null /*enabledComponents*/,
                                 null /*disabledComponents*/,
                                 INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED,
-                                0, PackageManager.INSTALL_REASON_UNKNOWN);
+                                0, PackageManager.INSTALL_REASON_UNKNOWN,
+                                null /*harmfulAppWarning*/);
                     }
                     return;
                 }
@@ -1755,7 +1708,8 @@
                             COMPONENT_ENABLED_STATE_DEFAULT);
                     final String enabledCaller = parser.getAttributeValue(null,
                             ATTR_ENABLED_CALLER);
-
+                    final String harmfulAppWarning =
+                            parser.getAttributeValue(null, ATTR_HARMFUL_APP_WARNING);
                     final int verifState = XmlUtils.readIntAttribute(parser,
                             ATTR_DOMAIN_VERIFICATON_STATE,
                             PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
@@ -1792,7 +1746,7 @@
                     ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
                             hidden, suspended, instantApp, virtualPreload, enabledCaller,
                             enabledComponents, disabledComponents, verifState, linkGeneration,
-                            installReason);
+                            installReason, harmfulAppWarning);
                 } else if (tagName.equals("preferred-activities")) {
                     readPreferredActivitiesLPw(parser, userId);
                 } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
@@ -2125,6 +2079,10 @@
                     serializer.attribute(null, ATTR_INSTALL_REASON,
                             Integer.toString(ustate.installReason));
                 }
+                if (ustate.harmfulAppWarning != null) {
+                    serializer.attribute(null, ATTR_HARMFUL_APP_WARNING,
+                            ustate.harmfulAppWarning);
+                }
                 if (!ArrayUtils.isEmpty(ustate.enabledComponents)) {
                     serializer.startTag(null, TAG_ENABLED_COMPONENTS);
                     for (final String name : ustate.enabledComponents) {
@@ -4347,6 +4305,22 @@
         return false;
     }
 
+    void setHarmfulAppWarningLPw(String packageName, CharSequence warning, int userId) {
+        final PackageSetting pkgSetting = mPackages.get(packageName);
+        if (pkgSetting == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        pkgSetting.setHarmfulAppWarning(userId, warning == null ? null : warning.toString());
+    }
+
+    String getHarmfulAppWarningLPr(String packageName, int userId) {
+        final PackageSetting pkgSetting = mPackages.get(packageName);
+        if (pkgSetting == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        return pkgSetting.getHarmfulAppWarning(userId);
+    }
+
     private static List<UserInfo> getAllUsers(UserManagerService userManager) {
         long id = Binder.clearCallingIdentity();
         try {
@@ -4493,11 +4467,14 @@
                 pw.print(ps.getNotLaunched(user.id) ? "l" : "L");
                 pw.print(ps.getInstantApp(user.id) ? "IA" : "ia");
                 pw.print(ps.getVirtulalPreload(user.id) ? "VPI" : "vpi");
+                String harmfulAppWarning = ps.getHarmfulAppWarning(user.id);
+                pw.print(harmfulAppWarning != null ? "HA" : "ha");
                 pw.print(",");
                 pw.print(ps.getEnabled(user.id));
                 String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
                 pw.print(",");
                 pw.print(lastDisabledAppCaller != null ? lastDisabledAppCaller : "?");
+                pw.print(",");
                 pw.println();
             }
             return;
@@ -4565,10 +4542,8 @@
             }
             pw.print(prefix); pw.print("  versionName="); pw.println(ps.pkg.mVersionName);
             pw.print(prefix); pw.print("  splits="); dumpSplitNames(pw, ps.pkg); pw.println();
-            final int apkSigningVersion = PackageParser.getApkSigningVersion(ps.pkg);
-            if (apkSigningVersion != PackageParser.APK_SIGNING_UNKNOWN) {
-                pw.print(prefix); pw.print("  apkSigningVersion="); pw.println(apkSigningVersion);
-            }
+            final int apkSigningVersion = ps.pkg.mSigningDetails.signatureSchemeVersion;
+            pw.print(prefix); pw.print("  apkSigningVersion="); pw.println(apkSigningVersion);
             pw.print(prefix); pw.print("  applicationInfo=");
                 pw.println(ps.pkg.applicationInfo.toString());
             pw.print(prefix); pw.print("  flags="); printFlags(pw, ps.pkg.applicationInfo.flags,
@@ -4772,6 +4747,12 @@
                         .getRuntimePermissionStates(user.id), dumpAll);
             }
 
+            String harmfulAppWarning = ps.getHarmfulAppWarning(user.id);
+            if (harmfulAppWarning != null) {
+                pw.print(prefix); pw.print("      harmfulAppWarning: ");
+                pw.println(harmfulAppWarning);
+            }
+
             if (permissionNames == null) {
                 ArraySet<String> cmp = ps.getDisabledComponents(user.id);
                 if (cmp != null && cmp.size() > 0) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7d57566..92fd904 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -394,7 +394,7 @@
     /**
      * Start an {@link IntentSender} when user is unlocked after disabling quiet mode.
      *
-     * @see {@link #trySetQuietModeEnabled(String, boolean, int, IntentSender)}
+     * @see {@link #requestQuietModeEnabled(String, boolean, int, IntentSender)}
      */
     private class DisableQuietModeUserUnlockedCallback extends IProgressListener.Stub {
         private final IntentSender mTarget;
@@ -823,7 +823,7 @@
     }
 
     @Override
-    public boolean trySetQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode,
+    public boolean requestQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode,
             int userHandle, @Nullable IntentSender target) {
         Preconditions.checkNotNull(callingPackage);
 
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index bfba700..50690cb 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -116,7 +116,10 @@
             UserManager.DISALLOW_USER_SWITCH,
             UserManager.DISALLOW_UNIFIED_PASSWORD,
             UserManager.DISALLOW_CONFIG_LOCATION_MODE,
-            UserManager.DISALLOW_AIRPLANE_MODE
+            UserManager.DISALLOW_AIRPLANE_MODE,
+            UserManager.DISALLOW_CONFIG_BRIGHTNESS,
+            UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
+            UserManager.DISALLOW_AMBIENT_DISPLAY
     });
 
     /**
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 34c3ce3..6e07eaa 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -27,7 +27,6 @@
 import android.companion.CompanionDeviceManager;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageList;
@@ -62,8 +61,6 @@
 import android.util.Xml;
 import com.android.internal.util.XmlUtils;
 import com.android.server.LocalServices;
-import com.android.server.pm.PackageManagerService;
-import com.android.server.pm.PackageSetting;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -1169,7 +1166,8 @@
         final String systemPackageName = mServiceInternal.getKnownPackageName(
                 PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
         final PackageParser.Package systemPackage = getPackage(systemPackageName);
-        return compareSignatures(systemPackage.mSignatures, pkg.mSignatures)
+        return compareSignatures(systemPackage.mSigningDetails.signatures,
+                pkg.mSigningDetails.signatures)
                 == PackageManager.SIGNATURE_MATCH;
     }
 
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 90ac4ab..786b998 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -29,7 +29,6 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
@@ -56,21 +55,17 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.RoSystemProperties;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemConfig;
 import com.android.server.Watchdog;
-import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.PackageManagerServiceUtils;
 import com.android.server.pm.PackageSetting;
-import com.android.server.pm.ProcessLoggingHandler;
 import com.android.server.pm.SharedUserSetting;
 import com.android.server.pm.UserManagerService;
 import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
@@ -1015,10 +1010,10 @@
         final PackageParser.Package systemPackage =
                 mPackageManagerInt.getPackage(systemPackageName);
         boolean allowed = (PackageManagerServiceUtils.compareSignatures(
-                                bp.getSourceSignatures(), pkg.mSignatures)
+                                bp.getSourceSignatures(), pkg.mSigningDetails.signatures)
                         == PackageManager.SIGNATURE_MATCH)
                 || (PackageManagerServiceUtils.compareSignatures(
-                                systemPackage.mSignatures, pkg.mSignatures)
+                systemPackage.mSigningDetails.signatures, pkg.mSigningDetails.signatures)
                         == PackageManager.SIGNATURE_MATCH);
         if (!allowed && (privilegedPermission || oemPermission)) {
             if (pkg.isSystem()) {
diff --git a/services/core/java/com/android/server/policy/BarController.java b/services/core/java/com/android/server/policy/BarController.java
index 10d9565..c906705 100644
--- a/services/core/java/com/android/server/policy/BarController.java
+++ b/services/core/java/com/android/server/policy/BarController.java
@@ -16,13 +16,16 @@
 
 package com.android.server.policy;
 
+import static com.android.server.wm.proto.BarControllerProto.STATE;
+import static com.android.server.wm.proto.BarControllerProto.TRANSIENT_STATE;
+
 import android.app.StatusBarManager;
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemClock;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.WindowManager;
 
 import com.android.server.LocalServices;
@@ -311,6 +314,13 @@
         throw new IllegalArgumentException("Unknown state " + state);
     }
 
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(STATE, mState);
+        proto.write(TRANSIENT_STATE, mTransientBarState);
+        proto.end(token);
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         if (mWin != null) {
             pw.print(prefix); pw.println(mTag);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 75d211b..d352559 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -127,6 +127,26 @@
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.FOCUSED_APP_TOKEN;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.FOCUSED_WINDOW;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.FORCE_STATUS_BAR;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.FORCE_STATUS_BAR_FROM_KEYGUARD;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.KEYGUARD_DELEGATE;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.KEYGUARD_DRAW_COMPLETE;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.KEYGUARD_OCCLUDED;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.KEYGUARD_OCCLUDED_CHANGED;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.KEYGUARD_OCCLUDED_PENDING;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.LAST_SYSTEM_UI_FLAGS;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.NAVIGATION_BAR;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.ORIENTATION;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.ORIENTATION_LISTENER;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.ROTATION;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.ROTATION_MODE;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.SCREEN_ON_FULLY;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.STATUS_BAR;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.TOP_FULLSCREEN_OPAQUE_OR_DIMMING_WINDOW;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.TOP_FULLSCREEN_OPAQUE_WINDOW;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.WINDOW_MANAGER_DRAW_COMPLETE;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -141,6 +161,7 @@
 import android.app.UiModeManager;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -172,7 +193,6 @@
 import android.media.IAudioService;
 import android.media.session.MediaSessionLegacyHelper;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.FactoryTest;
 import android.os.Handler;
@@ -250,6 +270,7 @@
 import com.android.internal.policy.PhoneWindow;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.ScreenshotHelper;
 import com.android.internal.util.ScreenShapeHelper;
 import com.android.internal.widget.PointerLocationView;
 import com.android.server.GestureLauncherService;
@@ -441,6 +462,7 @@
     AccessibilityManager mAccessibilityManager;
     BurnInProtectionHelper mBurnInProtectionHelper;
     AppOpsManager mAppOpsManager;
+    private ScreenshotHelper mScreenshotHelper;
     private boolean mHasFeatureWatch;
     private boolean mHasFeatureLeanback;
 
@@ -602,8 +624,6 @@
 
     PointerLocationView mPointerLocationView;
 
-    boolean mEmulateDisplayCutout = false;
-
     // During layout, the layer at which the doc window is placed.
     int mDockLayer;
     // During layout, this is the layer of the status bar.
@@ -699,6 +719,9 @@
     // Behavior of Back button while in-call and screen on
     int mIncallBackBehavior;
 
+    // Behavior of rotation suggestions. (See Settings.Secure.SHOW_ROTATION_SUGGESTION)
+    int mShowRotationSuggestions;
+
     Display mDisplay;
 
     int mLandscapeRotation = 0;  // default landscape rotation
@@ -953,11 +976,11 @@
             resolver.registerContentObserver(Settings.Secure.getUriFor(
                     Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS), false, this,
                     UserHandle.USER_ALL);
-            resolver.registerContentObserver(Settings.Global.getUriFor(
-                    Settings.Global.POLICY_CONTROL), false, this,
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    Settings.Secure.SHOW_ROTATION_SUGGESTIONS), false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(Settings.Global.getUriFor(
-                    Settings.Global.EMULATE_DISPLAY_CUTOUT), false, this,
+                    Settings.Global.POLICY_CONTROL), false, this,
                     UserHandle.USER_ALL);
             updateSettings();
         }
@@ -986,23 +1009,41 @@
     }
 
     class MyOrientationListener extends WindowOrientationListener {
-        private final Runnable mUpdateRotationRunnable = new Runnable() {
+
+        private SparseArray<Runnable> mRunnableCache;
+
+        MyOrientationListener(Context context, Handler handler) {
+            super(context, handler);
+            mRunnableCache = new SparseArray<>(5);
+        }
+
+        private class UpdateRunnable implements Runnable {
+            private final int mRotation;
+            UpdateRunnable(int rotation) {
+                mRotation = rotation;
+            }
+
             @Override
             public void run() {
                 // send interaction hint to improve redraw performance
                 mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0);
-                updateRotation(false);
+                if (showRotationChoice(mCurrentAppOrientation, mRotation)) {
+                    sendProposedRotationChangeToStatusBarInternal(mRotation);
+                } else {
+                    updateRotation(false);
+                }
             }
-        };
-
-        MyOrientationListener(Context context, Handler handler) {
-            super(context, handler);
         }
 
         @Override
         public void onProposedRotationChanged(int rotation) {
             if (localLOGV) Slog.v(TAG, "onProposedRotationChanged, rotation=" + rotation);
-            mHandler.post(mUpdateRotationRunnable);
+            Runnable r = mRunnableCache.get(rotation, null);
+            if (r == null){
+                r = new UpdateRunnable(rotation);
+                mRunnableCache.put(rotation, r);
+            }
+            mHandler.post(r);
         }
     }
     MyOrientationListener mOrientationListener;
@@ -1107,7 +1148,11 @@
             // orientation for a little bit, which can cause orientation
             // changes to lag, so we'd like to keep it always on.  (It will
             // still be turned off when the screen is off.)
-            return false;
+
+            // When locked we can provide rotation suggestions users can approve to change the
+            // current screen rotation. To do this the sensor needs to be running.
+            return mSupportAutoRotation &&
+                    mShowRotationSuggestions == Settings.Secure.SHOW_ROTATION_SUGGESTIONS_ENABLED;
         }
         return mSupportAutoRotation;
     }
@@ -1666,7 +1711,9 @@
 
         @Override
         public void run() {
-            takeScreenshot(mScreenshotType);
+            mScreenshotHelper.takeScreenshot(mScreenshotType,
+                    mStatusBar != null && mStatusBar.isVisibleLw(),
+                    mNavigationBar != null && mNavigationBar.isVisibleLw(), mHandler);
         }
     }
 
@@ -2144,6 +2191,7 @@
                         mWindowManagerFuncs.notifyKeyguardTrustedChanged();
                     }
                 });
+        mScreenshotHelper = new ScreenshotHelper(mContext);
     }
 
     /**
@@ -2256,9 +2304,13 @@
         // http://developer.android.com/guide/practices/screens_support.html#range
         // For car, ignore the dp limitation. It's physically impossible to rotate the car's screen
         // so if the orientation is forced, we need to respect that no matter what.
-        boolean isCar = mContext.getPackageManager().hasSystemFeature(
+        final boolean isCar = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_AUTOMOTIVE);
-        mForceDefaultOrientation = ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar) &&
+        // For TV, it's usually 960dp x 540dp, ignore the size limitation.
+        // so if the orientation is forced, we need to respect that no matter what.
+        final boolean isTv = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_LEANBACK);
+        mForceDefaultOrientation = ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar || isTv) &&
                 res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation) &&
                 // For debug purposes the next line turns this feature off with:
                 // $ adb shell setprop config.override_forced_orient true
@@ -2296,6 +2348,16 @@
                     Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR_DEFAULT,
                     UserHandle.USER_CURRENT);
 
+            // Configure rotation suggestions.
+            int showRotationSuggestions = Settings.Secure.getIntForUser(resolver,
+                    Settings.Secure.SHOW_ROTATION_SUGGESTIONS,
+                    Settings.Secure.SHOW_ROTATION_SUGGESTIONS_DEFAULT,
+                    UserHandle.USER_CURRENT);
+            if (mShowRotationSuggestions != showRotationSuggestions) {
+                mShowRotationSuggestions = showRotationSuggestions;
+                updateOrientationListenerLp(); // Enable, disable the orientation listener
+            }
+
             // Configure wake gesture.
             boolean wakeGestureEnabledSetting = Settings.Secure.getIntForUser(resolver,
                     Settings.Secure.WAKE_GESTURE_ENABLED, 0,
@@ -2345,10 +2407,6 @@
             if (mImmersiveModeConfirmation != null) {
                 mImmersiveModeConfirmation.loadSetting(mCurrentUserId);
             }
-            mEmulateDisplayCutout = Settings.Global.getInt(resolver,
-                    Settings.Global.EMULATE_DISPLAY_CUTOUT,
-                    Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF)
-                    != Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF;
         }
         synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
             PolicyControl.reloadFromSetting(mContext);
@@ -2684,6 +2742,11 @@
     }
 
     @Override
+    public void onOverlayChangedLw() {
+        onConfigurationChanged();
+    }
+
+    @Override
     public void onConfigurationChanged() {
         // TODO(multi-display): Define policy for secondary displays.
         Context uiContext = getSystemUiContext();
@@ -4383,7 +4446,7 @@
     /** {@inheritDoc} */
     @Override
     public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {
-        displayFrames.onBeginLayout(mEmulateDisplayCutout, mStatusBarHeight);
+        displayFrames.onBeginLayout();
         // TODO(multi-display): This doesn't seem right...Maybe only apply to default display?
         mSystemGestures.screenWidth = displayFrames.mUnrestricted.width();
         mSystemGestures.screenHeight = displayFrames.mUnrestricted.height();
@@ -4826,8 +4889,7 @@
         final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
 
         final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0
-                || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0
-                || (requestedSysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0;
+                || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
 
         final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
         final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
@@ -5709,100 +5771,6 @@
         setHdmiPlugged(!mHdmiPlugged);
     }
 
-    final Object mScreenshotLock = new Object();
-    ServiceConnection mScreenshotConnection = null;
-
-    final Runnable mScreenshotTimeout = new Runnable() {
-        @Override public void run() {
-            synchronized (mScreenshotLock) {
-                if (mScreenshotConnection != null) {
-                    mContext.unbindService(mScreenshotConnection);
-                    mScreenshotConnection = null;
-                    notifyScreenshotError();
-                }
-            }
-        }
-    };
-
-    // Assume this is called from the Handler thread.
-    private void takeScreenshot(final int screenshotType) {
-        synchronized (mScreenshotLock) {
-            if (mScreenshotConnection != null) {
-                return;
-            }
-            final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
-                    SYSUI_SCREENSHOT_SERVICE);
-            final Intent serviceIntent = new Intent();
-            serviceIntent.setComponent(serviceComponent);
-            ServiceConnection conn = new ServiceConnection() {
-                @Override
-                public void onServiceConnected(ComponentName name, IBinder service) {
-                    synchronized (mScreenshotLock) {
-                        if (mScreenshotConnection != this) {
-                            return;
-                        }
-                        Messenger messenger = new Messenger(service);
-                        Message msg = Message.obtain(null, screenshotType);
-                        final ServiceConnection myConn = this;
-                        Handler h = new Handler(mHandler.getLooper()) {
-                            @Override
-                            public void handleMessage(Message msg) {
-                                synchronized (mScreenshotLock) {
-                                    if (mScreenshotConnection == myConn) {
-                                        mContext.unbindService(mScreenshotConnection);
-                                        mScreenshotConnection = null;
-                                        mHandler.removeCallbacks(mScreenshotTimeout);
-                                    }
-                                }
-                            }
-                        };
-                        msg.replyTo = new Messenger(h);
-                        msg.arg1 = msg.arg2 = 0;
-                        if (mStatusBar != null && mStatusBar.isVisibleLw())
-                            msg.arg1 = 1;
-                        if (mNavigationBar != null && mNavigationBar.isVisibleLw())
-                            msg.arg2 = 1;
-                        try {
-                            messenger.send(msg);
-                        } catch (RemoteException e) {
-                        }
-                    }
-                }
-
-                @Override
-                public void onServiceDisconnected(ComponentName name) {
-                    synchronized (mScreenshotLock) {
-                        if (mScreenshotConnection != null) {
-                            mContext.unbindService(mScreenshotConnection);
-                            mScreenshotConnection = null;
-                            mHandler.removeCallbacks(mScreenshotTimeout);
-                            notifyScreenshotError();
-                        }
-                    }
-                }
-            };
-            if (mContext.bindServiceAsUser(serviceIntent, conn,
-                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
-                    UserHandle.CURRENT)) {
-                mScreenshotConnection = conn;
-                mHandler.postDelayed(mScreenshotTimeout, 10000);
-            }
-        }
-    }
-
-    /**
-     * Notifies the screenshot service to show an error.
-     */
-    private void notifyScreenshotError() {
-        // If the service process is killed, then ask it to clean up after itself
-        final ComponentName errorComponent = new ComponentName(SYSUI_PACKAGE,
-                SYSUI_SCREENSHOT_ERROR_RECEIVER);
-        Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT);
-        errorIntent.setComponent(errorComponent);
-        errorIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
-                Intent.FLAG_RECEIVER_FOREGROUND);
-        mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
-    }
 
     /** {@inheritDoc} */
     @Override
@@ -6227,6 +6195,16 @@
     }
 
     /**
+     * Notify the StatusBar that system rotation suggestion has changed.
+     */
+    private void sendProposedRotationChangeToStatusBarInternal(int rotation) {
+        StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+        if (statusBar != null) {
+            statusBar.onProposedRotationChanged(rotation);
+        }
+    }
+
+    /**
      * Returns true if the key can have global actions attached to it.
      * We reserve all power management keys for the system since they require
      * very careful handling.
@@ -6881,12 +6859,12 @@
     }
 
     @Override
-    public void dismissKeyguardLw(IKeyguardDismissCallback callback) {
+    public void dismissKeyguardLw(IKeyguardDismissCallback callback, CharSequence message) {
         if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "PWM.dismissKeyguardLw");
 
             // ask the keyguard to prompt the user to authenticate if necessary
-            mKeyguardDelegate.dismiss(callback);
+            mKeyguardDelegate.dismiss(callback, message);
         } else if (callback != null) {
             try {
                 callback.onDismissError();
@@ -7168,6 +7146,86 @@
         mOrientationListener.setCurrentRotation(rotation);
     }
 
+    public boolean showRotationChoice(int orientation, final int preferredRotation) {
+        // Rotation choice is only shown when the user is in locked mode.
+        if (mUserRotationMode != WindowManagerPolicy.USER_ROTATION_LOCKED) return false;
+
+        // We should only show a rotation choice if:
+        // 1. The rotation isn't forced by the lid, dock, demo, hdmi, vr, etc mode
+        // 2. The user choice won't be ignored due to screen orientation settings
+
+        // Determine if the rotation currently forced
+        if (mForceDefaultOrientation) {
+            return false; // Rotation is forced to default orientation
+
+        } else if (mLidState == LID_OPEN && mLidOpenRotation >= 0) {
+            return false; // Rotation is forced mLidOpenRotation
+
+        } else if (mDockMode == Intent.EXTRA_DOCK_STATE_CAR && !mCarDockEnablesAccelerometer) {
+            return false; // Rotation forced to mCarDockRotation
+
+        } else if ((mDockMode == Intent.EXTRA_DOCK_STATE_DESK
+                || mDockMode == Intent.EXTRA_DOCK_STATE_LE_DESK
+                || mDockMode == Intent.EXTRA_DOCK_STATE_HE_DESK)
+                && !mDeskDockEnablesAccelerometer) {
+            return false; // Rotation forced to mDeskDockRotation
+
+        } else if (mHdmiPlugged && mDemoHdmiRotationLock) {
+            return false; // Rotation forced to mDemoHdmiRotation
+
+        } else if (mHdmiPlugged && mDockMode == Intent.EXTRA_DOCK_STATE_UNDOCKED
+                && mUndockedHdmiRotation >= 0) {
+            return false; // Rotation forced to mUndockedHdmiRotation
+
+        } else if (mDemoRotationLock) {
+            return false; // Rotation forced to mDemoRotation
+
+        } else if (mPersistentVrModeEnabled) {
+            return false; // Rotation forced to mPortraitRotation
+
+        } else if (!mSupportAutoRotation) {
+            return false;
+        }
+
+        // Determine if the orientation will ignore user choice
+        switch (orientation) {
+            case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
+            case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
+            case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+            case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+            case ActivityInfo.SCREEN_ORIENTATION_LOCKED:
+                return false; // Forced into a particular rotation, no user choice
+
+            case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+            case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+            case ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR:
+            case ActivityInfo.SCREEN_ORIENTATION_SENSOR:
+                return false; // Sensor overrides user choice
+
+            case ActivityInfo.SCREEN_ORIENTATION_NOSENSOR:
+                // TODO Can sensor be used to indirectly determine the orientation?
+                return false;
+
+            case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
+                // If the user has locked sensor-based rotation, this behaves the same as landscape
+                return false; // User has locked the rotation, will behave as LANDSCAPE
+            case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
+                // If the user has locked sensor-based rotation, this behaves the same as portrait
+                return false; // User has locked the rotation, will behave as PORTRAIT
+            case ActivityInfo.SCREEN_ORIENTATION_USER:
+                // Works with any rotation except upside down
+                return (preferredRotation >= 0) && (preferredRotation != mUpsideDownRotation);
+            case ActivityInfo.SCREEN_ORIENTATION_FULL_USER:
+                // Works with any of the 4 rotations
+                return preferredRotation >= 0;
+
+            default:
+                // TODO: how to handle SCREEN_ORIENTATION_BEHIND, UNSET?
+                // For UNSPECIFIED use preferred orientation matching SCREEN_ORIENTATION_USER
+                return (preferredRotation >= 0) && (preferredRotation != mUpsideDownRotation);
+        }
+    }
+
     private boolean isLandscapeOrSeascape(int rotation) {
         return rotation == mLandscapeRotation || rotation == mSeascapeRotation;
     }
@@ -7845,8 +7903,11 @@
             // If the top fullscreen-or-dimming window is also the top fullscreen, respect
             // its light flag.
             vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
-            vis |= PolicyControl.getSystemUiVisibility(statusColorWin, null)
-                    & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+            if (!statusColorWin.isLetterboxedForDisplayCutoutLw()) {
+                // Only allow white status bar if the window was not letterboxed.
+                vis |= PolicyControl.getSystemUiVisibility(statusColorWin, null)
+                        & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+            }
         } else if (statusColorWin != null && statusColorWin.isDimming()) {
             // Otherwise if it's dimming, clear the light flag.
             vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
@@ -8218,6 +8279,40 @@
     @Override
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
+        proto.write(LAST_SYSTEM_UI_FLAGS, mLastSystemUiFlags);
+        proto.write(ROTATION_MODE, mUserRotationMode);
+        proto.write(ROTATION, mUserRotation);
+        proto.write(ORIENTATION, mCurrentAppOrientation);
+        proto.write(SCREEN_ON_FULLY, mScreenOnFully);
+        proto.write(KEYGUARD_DRAW_COMPLETE, mKeyguardDrawComplete);
+        proto.write(WINDOW_MANAGER_DRAW_COMPLETE, mWindowManagerDrawComplete);
+        if (mFocusedApp != null) {
+            proto.write(FOCUSED_APP_TOKEN, mFocusedApp.toString());
+        }
+        if (mFocusedWindow != null) {
+            mFocusedWindow.writeIdentifierToProto(proto, FOCUSED_WINDOW);
+        }
+        if (mTopFullscreenOpaqueWindowState != null) {
+            mTopFullscreenOpaqueWindowState.writeIdentifierToProto(
+                    proto, TOP_FULLSCREEN_OPAQUE_WINDOW);
+        }
+        if (mTopFullscreenOpaqueOrDimmingWindowState != null) {
+            mTopFullscreenOpaqueOrDimmingWindowState.writeIdentifierToProto(
+                    proto, TOP_FULLSCREEN_OPAQUE_OR_DIMMING_WINDOW);
+        }
+        proto.write(KEYGUARD_OCCLUDED, mKeyguardOccluded);
+        proto.write(KEYGUARD_OCCLUDED_CHANGED, mKeyguardOccludedChanged);
+        proto.write(KEYGUARD_OCCLUDED_PENDING, mPendingKeyguardOccluded);
+        proto.write(FORCE_STATUS_BAR, mForceStatusBar);
+        proto.write(FORCE_STATUS_BAR_FROM_KEYGUARD, mForceStatusBarFromKeyguard);
+        mStatusBarController.writeToProto(proto, STATUS_BAR);
+        mNavigationBarController.writeToProto(proto, NAVIGATION_BAR);
+        if (mOrientationListener != null) {
+            mOrientationListener.writeToProto(proto, ORIENTATION_LISTENER);
+        }
+        if (mKeyguardDelegate != null) {
+            mKeyguardDelegate.writeToProto(proto, KEYGUARD_DELEGATE);
+        }
         proto.end(token);
     }
 
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 40b656d..64a280c 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -176,6 +176,11 @@
     void onKeyguardOccludedChangedLw(boolean occluded);
 
     /**
+     * Called when the resource overlays change.
+     */
+    default void onOverlayChangedLw() {}
+
+    /**
      * Interface to the Window Manager state associated with a particular
      * window.  You can hold on to an instance of this interface from the call
      * to prepareAddWindow() until removeWindow().
@@ -446,6 +451,13 @@
          */
         public boolean isDimming();
 
+        /**
+         * Returns true if the window is letterboxed for the display cutout.
+         */
+        default boolean isLetterboxedForDisplayCutoutLw() {
+            return false;
+        }
+
         /** @return the current windowing mode of this window. */
         int getWindowingMode();
 
@@ -474,6 +486,11 @@
          * visible. That is, they have the permission {@link Manifest.permission#DEVICE_POWER}.
          */
         boolean canAcquireSleepToken();
+
+        /**
+         * Writes {@link com.android.server.wm.proto.IdentifierProto} to stream.
+         */
+        void writeIdentifierToProto(ProtoOutputStream proto, long fieldId);
     }
 
     /**
@@ -1380,8 +1397,10 @@
      * Ask the policy to dismiss the keyguard, if it is currently shown.
      *
      * @param callback Callback to be informed about the result.
+     * @param message A message that should be displayed in the keyguard.
      */
-    public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback);
+    public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback,
+            CharSequence message);
 
     /**
      * Ask the policy whether the Keyguard has drawn. If the Keyguard is disabled, this method
diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java
index 169fd27..1b5a521 100644
--- a/services/core/java/com/android/server/policy/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java
@@ -16,6 +16,9 @@
 
 package com.android.server.policy;
 
+import static com.android.server.wm.proto.WindowOrientationListenerProto.ENABLED;
+import static com.android.server.wm.proto.WindowOrientationListenerProto.ROTATION;
+
 import android.content.Context;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
@@ -24,13 +27,11 @@
 import android.os.Handler;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.text.TextUtils;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 import android.view.Surface;
 
 import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.List;
 
 /**
  * A special helper class used by the WindowManager
@@ -65,7 +66,7 @@
 
     /**
      * Creates a new WindowOrientationListener.
-     * 
+     *
      * @param context for the WindowOrientationListener.
      * @param handler Provides the Looper for receiving sensor updates.
      */
@@ -75,12 +76,12 @@
 
     /**
      * Creates a new WindowOrientationListener.
-     * 
+     *
      * @param context for the WindowOrientationListener.
      * @param handler Provides the Looper for receiving sensor updates.
      * @param rate at which sensor events are processed (see also
      * {@link android.hardware.SensorManager SensorManager}). Use the default
-     * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL 
+     * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
      * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
      *
      * This constructor is private since no one uses it.
@@ -232,6 +233,15 @@
      */
     public abstract void onProposedRotationChanged(int rotation);
 
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        synchronized (mLock) {
+            proto.write(ENABLED, mEnabled);
+            proto.write(ROTATION, mCurrentRotation);
+        }
+        proto.end(token);
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         synchronized (mLock) {
             pw.println(prefix + TAG);
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 58002bc..18f4a3c 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -1,6 +1,11 @@
 package com.android.server.policy.keyguard;
 
 import static android.view.Display.INVALID_DISPLAY;
+import static com.android.server.wm.proto.KeyguardServiceDelegateProto.INTERACTIVE_STATE;
+import static com.android.server.wm.proto.KeyguardServiceDelegateProto.OCCLUDED;
+import static com.android.server.wm.proto.KeyguardServiceDelegateProto.SCREEN_STATE;
+import static com.android.server.wm.proto.KeyguardServiceDelegateProto.SECURE;
+import static com.android.server.wm.proto.KeyguardServiceDelegateProto.SHOWING;
 
 import android.app.ActivityManager;
 import android.content.ComponentName;
@@ -15,6 +20,7 @@
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 import android.view.WindowManagerPolicyConstants;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -257,9 +263,9 @@
         mKeyguardState.occluded = isOccluded;
     }
 
-    public void dismiss(IKeyguardDismissCallback callback) {
+    public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
         if (mKeyguardService != null) {
-            mKeyguardService.dismiss(callback);
+            mKeyguardService.dismiss(callback, message);
         }
     }
 
@@ -406,6 +412,16 @@
         }
     }
 
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(SHOWING, mKeyguardState.showing);
+        proto.write(OCCLUDED, mKeyguardState.occluded);
+        proto.write(SECURE, mKeyguardState.secure);
+        proto.write(SCREEN_STATE, mKeyguardState.screenState);
+        proto.write(INTERACTIVE_STATE, mKeyguardState.interactiveState);
+        proto.end(token);
+    }
+
     public void dump(String prefix, PrintWriter pw) {
         pw.println(prefix + TAG);
         prefix += "  ";
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index 952e0b0..4e84868 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -74,9 +74,9 @@
     }
 
     @Override // Binder interface
-    public void dismiss(IKeyguardDismissCallback callback) {
+    public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
         try {
-            mService.dismiss(callback);
+            mService.dismiss(callback, message);
         } catch (RemoteException e) {
             Slog.w(TAG , "Remote Exception", e);
         }
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 6f005a3..a538967 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -20,6 +20,7 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerSaveState;
 import android.provider.Settings;
@@ -49,21 +50,6 @@
 
     public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE.
 
-    /** Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode. */
-    public static final int GPS_MODE_NO_CHANGE = 0;
-
-    /**
-     * Value of batterySaverGpsMode such that GPS is disabled when battery saver mode
-     * is enabled and the screen is off.
-     */
-    public static final int GPS_MODE_DISABLED_WHEN_SCREEN_OFF = 1;
-
-    /**
-     * Value of batterySaverGpsMode such that location should be disabled altogether
-     * when battery saver mode is enabled and the screen is off.
-     */
-    public static final int GPS_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2;
-
     // Secure setting for GPS behavior when battery saver mode is on.
     public static final String SECURE_KEY_GPS_MODE = "batterySaverGpsMode";
 
@@ -354,7 +340,7 @@
 
         // Get default value from Settings.Secure
         final int defaultGpsMode = Settings.Secure.getInt(mContentResolver, SECURE_KEY_GPS_MODE,
-                GPS_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
+                PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
         mGpsMode = parser.getInt(KEY_GPS_MODE, defaultGpsMode);
 
         // Non-device-specific parameters.
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverLocationPlugin.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverLocationPlugin.java
index 0af19b6..bd8baeb 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverLocationPlugin.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverLocationPlugin.java
@@ -16,11 +16,11 @@
 package com.android.server.power.batterysaver;
 
 import android.content.Context;
+import android.os.PowerManager;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.util.Slog;
 
-import com.android.server.power.BatterySaverPolicy;
 import com.android.server.power.batterysaver.BatterySaverController.Plugin;
 
 public class BatterySaverLocationPlugin implements Plugin {
@@ -53,7 +53,7 @@
     private void updateLocationState(BatterySaverController caller) {
         final boolean kill =
                 (caller.getBatterySaverPolicy().getGpsMode()
-                        == BatterySaverPolicy.GPS_MODE_ALL_DISABLED_WHEN_SCREEN_OFF) &&
+                        == PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF) &&
                 caller.isEnabled() && !caller.isInteractive();
 
         if (DEBUG) {
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index b31f4b3..2f5e2f8 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -555,7 +555,7 @@
             case StatsLog.CPU_TIME_PER_FREQ: {
                 List<StatsLogEventWrapper> ret = new ArrayList();
                 for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
-                    long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readDelta();
+                    long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
                     if (clusterTimeMs != null) {
                         for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
                             StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 3);
@@ -743,9 +743,13 @@
                 filter.addAction(Intent.ACTION_SHUTDOWN);
                 mContext.registerReceiverAsUser(
                         mShutdownEventReceiver, UserHandle.ALL, filter, null, null);
-
-                // Pull the latest state of UID->app name, version mapping when statsd starts.
-                informAllUidsLocked(mContext);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    // Pull the latest state of UID->app name, version mapping when statsd starts.
+                    informAllUidsLocked(mContext);
+                } finally {
+                    restoreCallingIdentity(token);
+                }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
                 forgetEverything();
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index f2098dc..a254ba2 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -24,6 +24,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.SurfaceControl.HIDDEN;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
@@ -245,6 +246,7 @@
 
     /** Whether this token should be boosted at the top of all app window tokens. */
     private boolean mNeedsZBoost;
+    private Letterbox mLetterbox;
 
     private final Point mTmpPoint = new Point();
     private final Rect mTmpRect = new Rect();
@@ -678,6 +680,7 @@
         if (destroyedSomething) {
             final DisplayContent dc = getDisplayContent();
             dc.assignWindowLayers(true /*setLayoutNeeded*/);
+            updateLetterbox(null);
         }
     }
 
@@ -944,6 +947,7 @@
     void removeChild(WindowState child) {
         super.removeChild(child);
         checkKeyguardFlagsChanged();
+        updateLetterbox(child);
     }
 
     private boolean waitingForReplacement() {
@@ -1409,6 +1413,33 @@
         return isInterestingAndDrawn;
     }
 
+    void updateLetterbox(WindowState winHint) {
+        final WindowState w = findMainWindow();
+        if (w != winHint && winHint != null && w != null) {
+            return;
+        }
+        final boolean needsLetterbox = w != null && w.isLetterboxedAppWindow()
+                && fillsParent() && w.hasDrawnLw();
+        if (needsLetterbox) {
+            if (mLetterbox == null) {
+                mLetterbox = new Letterbox(() -> makeChildSurface(null));
+            }
+            mLetterbox.setDimensions(mPendingTransaction, getParent().getBounds(), w.mFrame);
+        } else if (mLetterbox != null) {
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            // Make sure we have a transaction here, in case we're called outside of a transaction.
+            // This does not use mPendingTransaction, because SurfaceAnimator uses a
+            // global transaction in onAnimationEnd.
+            SurfaceControl.openTransaction();
+            try {
+                mLetterbox.hide(t);
+            } finally {
+                SurfaceControl.mergeToGlobalTransaction(t);
+                SurfaceControl.closeTransaction();
+            }
+        }
+    }
+
     @Override
     boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
         // For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent
@@ -1635,6 +1666,8 @@
             // the status bar). In that case we need to use the final frame.
             if (freeform) {
                 frame.set(win.mFrame);
+            } else if (win.isLetterboxedAppWindow()) {
+                frame.set(getTask().getBounds());
             } else {
                 frame.set(win.mContainingFrame);
             }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index df468ac..a8e00dd 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -342,9 +342,6 @@
             new TaskForResizePointSearchResult();
     private final ApplySurfaceChangesTransactionState mTmpApplySurfaceChangesTransactionState =
             new ApplySurfaceChangesTransactionState();
-    private final ScreenshotApplicationState mScreenshotApplicationState =
-            new ScreenshotApplicationState();
-    private final Transaction mTmpTransaction = new Transaction();
 
     // True if this display is in the process of being removed. Used to determine if the removal of
     // the display's direct children should be allowed.
@@ -661,10 +658,7 @@
             mWallpaperController.updateWallpaperVisibility();
         }
 
-        // Use mTmpTransaction instead of mPendingTransaction because we don't want to commit
-        // other changes in mPendingTransaction at this point.
-        w.handleWindowMovedIfNeeded(mTmpTransaction);
-        SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
+        w.handleWindowMovedIfNeeded(mPendingTransaction);
 
         final WindowStateAnimator winAnimator = w.mWinAnimator;
 
@@ -699,37 +693,11 @@
                     }
                 }
             }
-            final TaskStack stack = w.getStack();
-            if (!winAnimator.isWaitingForOpening()
-                    || (stack != null && stack.isAnimatingBounds())) {
-                // Updates the shown frame before we set up the surface. This is needed
-                // because the resizing could change the top-left position (in addition to
-                // size) of the window. setSurfaceBoundariesLocked uses mShownPosition to
-                // position the surface.
-                //
-                // If an animation is being started, we can't call this method because the
-                // animation hasn't processed its initial transformation yet, but in general
-                // we do want to update the position if the window is animating. We make an exception
-                // for the bounds animating state, where an application may have been waiting
-                // for an exit animation to start, but instead enters PiP. We need to ensure
-                // we always recompute the top-left in this case.
-                winAnimator.computeShownFrameLocked();
-            }
-            winAnimator.setSurfaceBoundariesLocked(mTmpRecoveringMemory /* recoveringMemory */);
-
-            // Since setSurfaceBoundariesLocked applies the clipping, we need to apply the position
-            // to the surface of the window container and also the position of the stack window
-            // container as well. Use mTmpTransaction instead of mPendingTransaction to avoid
-            // committing any existing changes in there.
-            w.updateSurfacePosition(mTmpTransaction);
-            if (stack != null) {
-                stack.updateSurfaceBounds(mTmpTransaction);
-            }
-            SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
         }
 
         final AppWindowToken atoken = w.mAppToken;
         if (atoken != null) {
+            atoken.updateLetterbox(w);
             final boolean updateAllDrawn = atoken.updateDrawnWindowStates(w);
             if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(atoken)) {
                 mTmpUpdateAllDrawn.add(atoken);
@@ -2845,6 +2813,7 @@
 
         mTmpRecoveringMemory = recoveringMemory;
         forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
+        prepareSurfaces();
 
         mService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
                 mTmpApplySurfaceChangesTransactionState.displayHasContent,
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index bd06192..13d0c86 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -101,7 +101,7 @@
     /** During layout, the current screen borders along which input method windows are placed. */
     public final Rect mDock = new Rect();
 
-    /** The display cutout used for layout (after rotation and emulation) */
+    /** The display cutout used for layout (after rotation) */
     @NonNull public DisplayCutout mDisplayCutout = DisplayCutout.NO_CUTOUT;
 
     /** The cutout as supplied by display info */
@@ -134,7 +134,7 @@
                 ? info.displayCutout : DisplayCutout.NO_CUTOUT;
     }
 
-    public void onBeginLayout(boolean emulateDisplayCutout, int statusBarHeight) {
+    public void onBeginLayout() {
         switch (mRotation) {
             case ROTATION_90:
                 mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.top;
@@ -172,12 +172,8 @@
         mStable.set(mUnrestricted);
         mStableFullscreen.set(mUnrestricted);
         mCurrent.set(mUnrestricted);
-        mDisplayCutout = mDisplayInfoCutout;
-        if (emulateDisplayCutout) {
-            setEmulatedDisplayCutout((int) (statusBarHeight * 0.8));
-        }
-        mDisplayCutout = mDisplayCutout.calculateRelativeTo(mOverscan);
 
+        mDisplayCutout = mDisplayInfoCutout.calculateRelativeTo(mOverscan);
         mDisplayCutoutSafe.set(Integer.MIN_VALUE, Integer.MIN_VALUE,
                 Integer.MAX_VALUE, Integer.MAX_VALUE);
         if (!mDisplayCutout.isEmpty()) {
@@ -201,51 +197,6 @@
         return mDock.bottom - mCurrent.bottom;
     }
 
-    private void setEmulatedDisplayCutout(int height) {
-        final boolean swappedDimensions = mRotation == ROTATION_90 || mRotation == ROTATION_270;
-
-        final int screenWidth = swappedDimensions ? mDisplayHeight : mDisplayWidth;
-        final int screenHeight = swappedDimensions ? mDisplayWidth : mDisplayHeight;
-
-        final int widthTop = (int) (screenWidth * 0.3);
-        final int widthBottom = widthTop - height;
-
-        switch (mRotation) {
-            case ROTATION_90:
-                mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList(
-                        new Point(0, (screenWidth - widthTop) / 2),
-                        new Point(height, (screenWidth - widthBottom) / 2),
-                        new Point(height, (screenWidth + widthBottom) / 2),
-                        new Point(0, (screenWidth + widthTop) / 2)
-                ));
-                break;
-            case ROTATION_180:
-                mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList(
-                        new Point((screenWidth - widthTop) / 2, screenHeight),
-                        new Point((screenWidth - widthBottom) / 2, screenHeight - height),
-                        new Point((screenWidth + widthBottom) / 2, screenHeight - height),
-                        new Point((screenWidth + widthTop) / 2, screenHeight)
-                ));
-                break;
-            case ROTATION_270:
-                mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList(
-                        new Point(screenHeight, (screenWidth - widthTop) / 2),
-                        new Point(screenHeight - height, (screenWidth - widthBottom) / 2),
-                        new Point(screenHeight - height, (screenWidth + widthBottom) / 2),
-                        new Point(screenHeight, (screenWidth + widthTop) / 2)
-                ));
-                break;
-            default:
-                mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList(
-                        new Point((screenWidth - widthTop) / 2, 0),
-                        new Point((screenWidth - widthBottom) / 2, height),
-                        new Point((screenWidth + widthBottom) / 2, height),
-                        new Point((screenWidth + widthTop) / 2, 0)
-                ));
-                break;
-        }
-    }
-
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         mStable.writeToProto(proto, STABLE_BOUNDS);
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
new file mode 100644
index 0000000..8fa79ab
--- /dev/null
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+
+package com.android.server.wm;
+
+import static android.view.SurfaceControl.HIDDEN;
+
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import java.util.function.Supplier;
+
+/**
+ * Manages a set of {@link SurfaceControl}s to draw a black letterbox between an
+ * outer rect and an inner rect.
+ */
+public class Letterbox {
+
+    private static final Rect EMPTY_RECT = new Rect();
+
+    private final Supplier<SurfaceControl.Builder> mFactory;
+    private final Rect mOuter = new Rect();
+    private final Rect mInner = new Rect();
+    private final LetterboxSurface mTop = new LetterboxSurface("top");
+    private final LetterboxSurface mLeft = new LetterboxSurface("left");
+    private final LetterboxSurface mBottom = new LetterboxSurface("bottom");
+    private final LetterboxSurface mRight = new LetterboxSurface("right");
+
+    /**
+     * Constructs a Letterbox.
+     *
+     * @param surfaceControlFactory a factory for creating the managed {@link SurfaceControl}s
+     */
+    public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory) {
+        mFactory = surfaceControlFactory;
+    }
+
+    /**
+     * Sets the dimensions of the the letterbox, such that the area between the outer and inner
+     * frames will be covered by black color surfaces.
+     *
+     * @param t     a transaction in which to set the dimensions
+     * @param outer the outer frame of the letterbox (this frame will be black, except the area
+     *              that intersects with the {code inner} frame).
+     * @param inner the inner frame of the letterbox (this frame will be clear)
+     */
+    public void setDimensions(SurfaceControl.Transaction t, Rect outer, Rect inner) {
+        mOuter.set(outer);
+        mInner.set(inner);
+
+        mTop.setRect(t, outer.left, outer.top, inner.right, inner.top);
+        mLeft.setRect(t, outer.left, inner.top, inner.left, outer.bottom);
+        mBottom.setRect(t, inner.left, inner.bottom, outer.right, outer.bottom);
+        mRight.setRect(t, inner.right, outer.top, outer.right, inner.bottom);
+    }
+
+    /**
+     * Hides the letterbox.
+     *
+     * @param t a transaction in which to hide the letterbox
+     */
+    public void hide(SurfaceControl.Transaction t) {
+        setDimensions(t, EMPTY_RECT, EMPTY_RECT);
+    }
+
+    /**
+     * Destroys the managed {@link SurfaceControl}s.
+     */
+    public void destroy() {
+        mOuter.setEmpty();
+        mInner.setEmpty();
+
+        mTop.destroy();
+        mLeft.destroy();
+        mBottom.destroy();
+        mRight.destroy();
+    }
+
+    private class LetterboxSurface {
+
+        private final String mType;
+        private SurfaceControl mSurface;
+
+        private int mLastLeft = 0;
+        private int mLastTop = 0;
+        private int mLastRight = 0;
+        private int mLastBottom = 0;
+
+        public LetterboxSurface(String type) {
+            mType = type;
+        }
+
+        public void setRect(SurfaceControl.Transaction t,
+                int left, int top, int right, int bottom) {
+            if (mLastLeft == left && mLastTop == top
+                    && mLastRight == right && mLastBottom == bottom) {
+                // Nothing changed.
+                return;
+            }
+
+            if (left < right && top < bottom) {
+                if (mSurface == null) {
+                    createSurface();
+                }
+                t.setPosition(mSurface, left, top);
+                t.setSize(mSurface, right - left, bottom - top);
+                t.show(mSurface);
+            } else if (mSurface != null) {
+                t.hide(mSurface);
+            }
+
+            mLastLeft = left;
+            mLastTop = top;
+            mLastRight = right;
+            mLastBottom = bottom;
+        }
+
+        private void createSurface() {
+            mSurface = mFactory.get().setName("Letterbox - " + mType)
+                    .setFlags(HIDDEN).setColorLayer(true).build();
+            mSurface.setLayer(-1);
+            mSurface.setColor(new float[]{0, 0, 0});
+        }
+
+        public void destroy() {
+            if (mSurface != null) {
+                mSurface.destroy();
+                mSurface = null;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
index d2cbf88..33e560f 100644
--- a/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
+++ b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
@@ -33,7 +33,7 @@
 // the surface control.
 //
 // See cts/hostsidetests/../../SurfaceTraceReceiver.java for parsing side.
-class RemoteSurfaceTrace extends SurfaceControlWithBackground {
+class RemoteSurfaceTrace extends SurfaceControl {
     static final String TAG = "RemoteSurfaceTrace";
 
     final FileDescriptor mWriteFd;
@@ -42,7 +42,7 @@
     final WindowManagerService mService;
     final WindowState mWindow;
 
-    RemoteSurfaceTrace(FileDescriptor fd, SurfaceControlWithBackground wrapped,
+    RemoteSurfaceTrace(FileDescriptor fd, SurfaceControl wrapped,
             WindowState window) {
         super(wrapped);
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2a77c92..2cc96c9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -86,7 +86,6 @@
 import static com.android.server.wm.WindowManagerService.logSurface;
 import static com.android.server.wm.WindowSurfacePlacer.SET_FORCE_HIDING_CHANGED;
 import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
-import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
 import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
@@ -968,9 +967,7 @@
                 doRequest = true;
             }
         }
-        if ((bulkUpdateParams & SET_TURN_ON_SCREEN) != 0) {
-            mService.mTurnOnScreen = true;
-        }
+
         if ((bulkUpdateParams & SET_WALLPAPER_ACTION_PENDING) != 0) {
             mWallpaperActionPending = true;
         }
diff --git a/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java b/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
deleted file mode 100644
index 7c5bd43..0000000
--- a/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
+++ /dev/null
@@ -1,334 +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 static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-
-import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
-import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
-import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
-
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
-import android.view.SurfaceControl;
-
-/**
- * SurfaceControl extension that has black background behind navigation bar area for fullscreen
- * letterboxed apps.
- */
-class SurfaceControlWithBackground extends SurfaceControl {
-    // SurfaceControl that holds the background.
-    private SurfaceControl mBackgroundControl;
-
-    // Flag that defines whether the background should be shown.
-    private boolean mVisible;
-
-    // Way to communicate with corresponding window.
-    private WindowSurfaceController mWindowSurfaceController;
-
-    // Rect to hold task bounds when computing metrics for background.
-    private Rect mTmpContainerRect = new Rect();
-
-    // Last metrics applied to the main SurfaceControl.
-    private float mLastWidth, mLastHeight;
-    private float mLastDsDx = 1, mLastDsDy = 1;
-    private float mLastX, mLastY;
-
-    // SurfaceFlinger doesn't support crop rectangles where width or height is non-positive.
-    // If we just set an empty crop it will behave as if there is no crop at all.
-    // To fix this we explicitly hide the surface and won't let it to be shown.
-    private boolean mHiddenForCrop = false;
-
-    public SurfaceControlWithBackground(SurfaceControlWithBackground other) {
-        super(other);
-        mBackgroundControl = other.mBackgroundControl;
-        mVisible = other.mVisible;
-        mWindowSurfaceController = other.mWindowSurfaceController;
-    }
-
-    public SurfaceControlWithBackground(String name, SurfaceControl.Builder b,
-            int windowType, int w, int h,
-            WindowSurfaceController windowSurfaceController) throws OutOfResourcesException {
-        super(b.build());
-
-        // We should only show background behind app windows that are letterboxed in a task.
-        if ((windowType != TYPE_BASE_APPLICATION && windowType != TYPE_APPLICATION_STARTING)
-                || !windowSurfaceController.mAnimator.mWin.isLetterboxedAppWindow()) {
-            return;
-        }
-        mWindowSurfaceController = windowSurfaceController;
-        mLastWidth = w;
-        mLastHeight = h;
-        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
-        mBackgroundControl = b.setName("Background for - " + name)
-                .setSize(mTmpContainerRect.width(), mTmpContainerRect.height())
-                .setFormat(OPAQUE)
-                .setColorLayer(true)
-                .build();
-    }
-
-    @Override
-    public void setAlpha(float alpha) {
-        super.setAlpha(alpha);
-
-        if (mBackgroundControl == null) {
-            return;
-        }
-        mBackgroundControl.setAlpha(alpha);
-    }
-
-    @Override
-    public void setLayer(int zorder) {
-        super.setLayer(zorder);
-
-        if (mBackgroundControl == null) {
-            return;
-        }
-        // TODO: Use setRelativeLayer(Integer.MIN_VALUE) when it's fixed.
-        mBackgroundControl.setLayer(zorder - 1);
-    }
-
-    @Override
-    public void setPosition(float x, float y) {
-        super.setPosition(x, y);
-
-        if (mBackgroundControl == null) {
-            return;
-        }
-        mLastX = x;
-        mLastY = y;
-        updateBgPosition();
-    }
-
-    private void updateBgPosition() {
-        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
-        final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame;
-        final float offsetX = (mTmpContainerRect.left - winFrame.left) * mLastDsDx;
-        final float offsetY = (mTmpContainerRect.top - winFrame.top) * mLastDsDy;
-        mBackgroundControl.setPosition(mLastX + offsetX, mLastY + offsetY);
-    }
-
-    @Override
-    public void setSize(int w, int h) {
-        super.setSize(w, h);
-
-        if (mBackgroundControl == null) {
-            return;
-        }
-        mLastWidth = w;
-        mLastHeight = h;
-        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
-        mBackgroundControl.setSize(mTmpContainerRect.width(), mTmpContainerRect.height());
-    }
-
-    @Override
-    public void setWindowCrop(Rect crop) {
-        super.setWindowCrop(crop);
-
-        if (mBackgroundControl == null) {
-            return;
-        }
-        calculateBgCrop(crop);
-        mBackgroundControl.setWindowCrop(mTmpContainerRect);
-        mHiddenForCrop = mTmpContainerRect.isEmpty();
-        updateBackgroundVisibility();
-    }
-
-    @Override
-    public void setFinalCrop(Rect crop) {
-        super.setFinalCrop(crop);
-
-        if (mBackgroundControl == null) {
-            return;
-        }
-        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
-        mBackgroundControl.setFinalCrop(mTmpContainerRect);
-    }
-
-    /**
-     * Compute background crop based on current animation progress for main surface control and
-     * update {@link #mTmpContainerRect} with new values.
-     */
-    private void calculateBgCrop(Rect crop) {
-        // Track overall progress of animation by computing cropped portion of status bar.
-        final Rect contentInsets = mWindowSurfaceController.mAnimator.mWin.mContentInsets;
-        float d = contentInsets.top == 0 ? 0 : (float) crop.top / contentInsets.top;
-        if (d > 1.f) {
-            // We're running expand animation from launcher, won't compute custom bg crop here.
-            mTmpContainerRect.setEmpty();
-            return;
-        }
-
-        // Compute new scaled width and height for background that will depend on current animation
-        // progress. Those consist of current crop rect for the main surface + scaled areas outside
-        // of letterboxed area.
-        // TODO: Because the progress is computed with low precision we're getting smaller values
-        // for background width/height then screen size at the end of the animation. Will round when
-        // the value is smaller then some empiric epsilon. However, this should be fixed by
-        // computing correct frames for letterboxed windows in WindowState.
-        d = d < 0.025f ? 0 : d;
-        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
-        int backgroundWidth = 0, backgroundHeight = 0;
-        // Compute additional offset for the background when app window is positioned not at (0,0).
-        // E.g. landscape with navigation bar on the left.
-        final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame;
-        int offsetX = (int)((winFrame.left - mTmpContainerRect.left) * mLastDsDx),
-                offsetY = (int) ((winFrame.top - mTmpContainerRect.top) * mLastDsDy);
-
-        // Position and size background.
-        final int bgPosition = mWindowSurfaceController.mAnimator.mService.getNavBarPosition();
-
-        switch (bgPosition) {
-            case NAV_BAR_LEFT:
-                backgroundWidth = (int) ((mTmpContainerRect.width() - mLastWidth) * (1 - d) + 0.5);
-                backgroundHeight = crop.height();
-                offsetX += crop.left - backgroundWidth;
-                offsetY += crop.top;
-                break;
-            case NAV_BAR_RIGHT:
-                backgroundWidth = (int) ((mTmpContainerRect.width() - mLastWidth) * (1 - d) + 0.5);
-                backgroundHeight = crop.height();
-                offsetX += crop.right;
-                offsetY += crop.top;
-                break;
-            case NAV_BAR_BOTTOM:
-                backgroundWidth = crop.width();
-                backgroundHeight = (int) ((mTmpContainerRect.height() - mLastHeight) * (1 - d)
-                        + 0.5);
-                offsetX += crop.left;
-                offsetY += crop.bottom;
-                break;
-        }
-        mTmpContainerRect.set(offsetX, offsetY, offsetX + backgroundWidth,
-                offsetY + backgroundHeight);
-    }
-
-    @Override
-    public void setLayerStack(int layerStack) {
-        super.setLayerStack(layerStack);
-
-        if (mBackgroundControl == null) {
-            return;
-        }
-        mBackgroundControl.setLayerStack(layerStack);
-    }
-
-    @Override
-    public void setOpaque(boolean isOpaque) {
-        super.setOpaque(isOpaque);
-        updateBackgroundVisibility();
-    }
-
-    @Override
-    public void setSecure(boolean isSecure) {
-        super.setSecure(isSecure);
-    }
-
-    @Override
-    public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
-        super.setMatrix(dsdx, dtdx, dtdy, dsdy);
-
-        if (mBackgroundControl == null) {
-            return;
-        }
-        mBackgroundControl.setMatrix(dsdx, dtdx, dtdy, dsdy);
-        mLastDsDx = dsdx;
-        mLastDsDy = dsdy;
-        updateBgPosition();
-    }
-
-    @Override
-    public void hide() {
-        super.hide();
-        mVisible = false;
-        updateBackgroundVisibility();
-    }
-
-    @Override
-    public void show() {
-        super.show();
-        mVisible = true;
-        updateBackgroundVisibility();
-    }
-
-    @Override
-    public void destroy() {
-        super.destroy();
-
-        if (mBackgroundControl == null) {
-            return;
-        }
-        mBackgroundControl.destroy();
-    }
-
-    @Override
-    public void release() {
-        super.release();
-
-        if (mBackgroundControl == null) {
-            return;
-        }
-        mBackgroundControl.release();
-    }
-
-    @Override
-    public void setTransparentRegionHint(Region region) {
-        super.setTransparentRegionHint(region);
-
-        if (mBackgroundControl == null) {
-            return;
-        }
-        mBackgroundControl.setTransparentRegionHint(region);
-    }
-
-    @Override
-    public void deferTransactionUntil(IBinder handle, long frame) {
-        super.deferTransactionUntil(handle, frame);
-
-        if (mBackgroundControl == null) {
-            return;
-        }
-        mBackgroundControl.deferTransactionUntil(handle, frame);
-    }
-
-    @Override
-    public void deferTransactionUntil(Surface barrier, long frame) {
-        super.deferTransactionUntil(barrier, frame);
-
-        if (mBackgroundControl == null) {
-            return;
-        }
-        mBackgroundControl.deferTransactionUntil(barrier, frame);
-    }
-
-    private void updateBackgroundVisibility() {
-        if (mBackgroundControl == null) {
-            return;
-        }
-        final AppWindowToken appWindowToken = mWindowSurfaceController.mAnimator.mWin.mAppToken;
-        if (!mHiddenForCrop && mVisible && appWindowToken != null && appWindowToken.fillsParent()) {
-            mBackgroundControl.show();
-        } else {
-            mBackgroundControl.hide();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 7024fb1..8a36226 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -49,6 +49,7 @@
 
 import android.annotation.CallSuper;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.RemoteException;
@@ -150,6 +151,7 @@
      * For {@link #prepareSurfaces}.
      */
     final Rect mTmpDimBoundsRect = new Rect();
+    private final Point mLastSurfaceSize = new Point();
 
     TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
         super(service);
@@ -749,7 +751,13 @@
         }
 
         final Rect stackBounds = getBounds();
-        transaction.setSize(mSurfaceControl, stackBounds.width(), stackBounds.height());
+        final int width = stackBounds.width();
+        final int height = stackBounds.height();
+        if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
+            return;
+        }
+        transaction.setSize(mSurfaceControl, width, height);
+        mLastSurfaceSize.set(width, height);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 3efd6ac..cec13ab 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -292,9 +292,6 @@
         if ((bulkUpdateParams & WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE) != 0) {
             builder.append(" ORIENTATION_CHANGE_COMPLETE");
         }
-        if ((bulkUpdateParams & WindowSurfacePlacer.SET_TURN_ON_SCREEN) != 0) {
-            builder.append(" TURN_ON_SCREEN");
-        }
         return builder.toString();
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index c267c70..42c6ec2 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -95,6 +95,7 @@
     protected final WindowManagerService mService;
 
     private final Point mTmpPos = new Point();
+    protected final Point mLastSurfacePosition = new Point();
 
     /** Total number of elements in this subtree, including our own hierarchy element. */
     private int mTreeWeight = 1;
@@ -1179,7 +1180,12 @@
         }
 
         getRelativePosition(mTmpPos);
+        if (mTmpPos.equals(mLastSurfacePosition)) {
+            return;
+        }
+
         transaction.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
+        mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y);
 
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             mChildren.get(i).updateSurfacePosition(transaction);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fcc9988..7b33533 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2941,10 +2941,10 @@
     }
 
     @Override
-    public void dismissKeyguard(IKeyguardDismissCallback callback) {
+    public void dismissKeyguard(IKeyguardDismissCallback callback, CharSequence message) {
         checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard");
         synchronized(mWindowMap) {
-            mPolicy.dismissKeyguardLw(callback);
+            mPolicy.dismissKeyguardLw(callback, message);
         }
     }
 
@@ -6596,6 +6596,13 @@
         }
     }
 
+    public void onOverlayChanged() {
+        synchronized (mWindowMap) {
+            mPolicy.onOverlayChangedLw();
+            requestTraversal();
+        }
+    }
+
     public void onDisplayChanged(int displayId) {
         synchronized (mWindowMap) {
             final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index b9dc9db..e24c393 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -231,7 +231,7 @@
     }
 
     private int runDismissKeyguard(PrintWriter pw) throws RemoteException {
-        mInterface.dismissKeyguard(null /* callback */);
+        mInterface.dismissKeyguard(null /* callback */, null /* message */);
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b3809dd..d5a1680 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -20,16 +20,20 @@
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.SurfaceControl.Transaction;
+import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
@@ -198,7 +202,6 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Comparator;
-import java.util.LinkedList;
 import java.util.function.Predicate;
 
 /** A window in the window manager. */
@@ -2977,7 +2980,29 @@
 
     /** @return true when the window is in fullscreen task, but has non-fullscreen bounds set. */
     boolean isLetterboxedAppWindow() {
-        return !isInMultiWindowMode() && mAppToken != null && !mAppToken.matchParentBounds();
+        return !isInMultiWindowMode() && mAppToken != null && !mAppToken.matchParentBounds()
+                || isLetterboxedForDisplayCutoutLw();
+    }
+
+    @Override
+    public boolean isLetterboxedForDisplayCutoutLw() {
+        if (mAppToken == null) {
+            // Only windows with an AppWindowToken are letterboxed.
+            return false;
+        }
+        if (getDisplayContent().getDisplayInfo().displayCutout == null) {
+            // No cutout, no letterbox.
+            return false;
+        }
+        if ((mAttrs.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0) {
+            // Layout in cutout, no letterbox.
+            return false;
+        }
+        // TODO: handle dialogs and other non-filling windows
+        // Letterbox if any fullscreen mode is set.
+        final int fl = mAttrs.flags;
+        final int sysui = mSystemUiVisibility;
+        return (fl & FLAG_FULLSCREEN) != 0 || (sysui & (SYSTEM_UI_FLAG_FULLSCREEN)) != 0;
     }
 
     boolean isDragResizeChanged() {
@@ -3136,7 +3161,8 @@
         proto.end(token);
     }
 
-    void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
+    @Override
+    public void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         proto.write(HASH_CODE, System.identityHashCode(this));
         proto.write(USER_ID, UserHandle.getUserId(mOwnerUid));
@@ -4487,6 +4513,7 @@
 
         // Leash is now responsible for position, so set our position to 0.
         t.setPosition(mSurfaceControl, 0, 0);
+        mLastSurfacePosition.set(0, 0);
     }
 
     @Override
@@ -4502,8 +4529,9 @@
         }
 
         transformFrameToSurfacePosition(mFrame.left, mFrame.top, mSurfacePosition);
-        if (!mSurfaceAnimator.hasLeash()) {
+        if (!mSurfaceAnimator.hasLeash() && !mLastSurfacePosition.equals(mSurfacePosition)) {
             t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
+            mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 96b0bd5..ba5156b 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -41,7 +41,6 @@
 import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
 import static com.android.server.wm.WindowManagerService.logWithStack;
 import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
-import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
 import static com.android.server.wm.proto.WindowStateAnimatorProto.DRAW_STATE;
 import static com.android.server.wm.proto.WindowStateAnimatorProto.LAST_CLIP_RECT;
 import static com.android.server.wm.proto.WindowStateAnimatorProto.SURFACE;
@@ -1165,7 +1164,7 @@
                     // potentially occurring while turning off the screen. This would lead to the
                     // screen incorrectly turning back on.
                     if (!mService.mPowerManager.isInteractive()) {
-                        mAnimator.mBulkUpdateParams |= SET_TURN_ON_SCREEN;
+                        mService.mTurnOnScreen = true;
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index e26a362..2f38556 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -53,7 +53,7 @@
 
     final WindowStateAnimator mAnimator;
 
-    SurfaceControlWithBackground mSurfaceControl;
+    SurfaceControl mSurfaceControl;
 
     // Should only be set from within setShown().
     private boolean mSurfaceShown = false;
@@ -108,13 +108,11 @@
                 .setFormat(format)
                 .setFlags(flags)
                 .setMetadata(windowType, ownerUid);
-        mSurfaceControl = new SurfaceControlWithBackground(
-                name, b, windowType, w, h, this);
+        mSurfaceControl = b.build();
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
         if (mService.mRoot.mSurfaceTraceEnabled) {
-            mSurfaceControl = new RemoteSurfaceTrace(
-                    mService.mRoot.mSurfaceTraceFd.getFileDescriptor(), mSurfaceControl, win);
+            installRemoteTrace(mService.mRoot.mSurfaceTraceFd.getFileDescriptor());
         }
     }
 
@@ -123,7 +121,7 @@
     }
 
     void removeRemoteTrace() {
-        mSurfaceControl = new SurfaceControlWithBackground(mSurfaceControl);
+        mSurfaceControl = new SurfaceControl(mSurfaceControl);
     }
 
 
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index a512fdf..017b325 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -86,8 +86,7 @@
     static final int SET_WALLPAPER_MAY_CHANGE           = 1 << 1;
     static final int SET_FORCE_HIDING_CHANGED           = 1 << 2;
     static final int SET_ORIENTATION_CHANGE_COMPLETE    = 1 << 3;
-    static final int SET_TURN_ON_SCREEN                 = 1 << 4;
-    static final int SET_WALLPAPER_ACTION_PENDING       = 1 << 5;
+    static final int SET_WALLPAPER_ACTION_PENDING       = 1 << 4;
 
     private boolean mTraversalScheduled;
     private int mDeferDepth = 0;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 29ac4ce..36de3d1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -19,6 +19,7 @@
 import android.app.admin.IDevicePolicyManager;
 import android.content.ComponentName;
 import android.os.PersistableBundle;
+import android.os.UserHandle;
 import android.security.keymaster.KeymasterCertificateChain;
 import android.security.keystore.ParcelableKeyGenParameterSpec;
 
@@ -38,6 +39,12 @@
  */
 abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
     /**
+     * To be called by {@link DevicePolicyManagerService#Lifecycle} when the service is started.
+     *
+     * @see {@link SystemService#onStart}.
+     */
+    abstract void handleStart();
+    /**
      * To be called by {@link DevicePolicyManagerService#Lifecycle} during the various boot phases.
      *
      * @see {@link SystemService#onBootPhase}.
@@ -97,4 +104,9 @@
             byte[] cert, byte[] chain, boolean isUserSelectable) {
         return false;
     }
+
+    @Override
+    public boolean startUserInBackground(ComponentName who, UserHandle userHandle) {
+        return false;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3592fc0..bf2b137 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -54,7 +54,6 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
-import static android.app.admin.DevicePolicyManager.START_USER_IN_BACKGROUND;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -96,6 +95,7 @@
 import android.app.admin.SystemUpdatePolicy;
 import android.app.backup.IBackupManager;
 import android.app.trust.TrustManager;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -150,6 +150,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
+import android.os.UserManagerInternal.UserRestrictionsListener;
 import android.os.storage.StorageManager;
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsInternal;
@@ -401,6 +402,7 @@
     final IPackageManager mIPackageManager;
     final UserManager mUserManager;
     final UserManagerInternal mUserManagerInternal;
+    final UsageStatsManagerInternal mUsageStatsManagerInternal;
     final TelephonyManager mTelephonyManager;
     private final LockPatternUtils mLockPatternUtils;
     private final DevicePolicyConstants mConstants;
@@ -504,6 +506,7 @@
         @Override
         public void onStart() {
             publishBinderService(Context.DEVICE_POLICY_SERVICE, mService);
+            mService.handleStart();
         }
 
         @Override
@@ -700,6 +703,33 @@
         }
     };
 
+    protected static class RestrictionsListener implements UserRestrictionsListener {
+        private Context mContext;
+
+        public RestrictionsListener(Context context) {
+            mContext = context;
+        }
+
+        public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
+                Bundle prevRestrictions) {
+            final boolean newlyDisallowed =
+                    newRestrictions.getBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
+            final boolean previouslyDisallowed =
+                    prevRestrictions.getBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
+            final boolean restrictionChanged = (newlyDisallowed != previouslyDisallowed);
+
+            if (restrictionChanged) {
+                // Notify ManagedProvisioning to update the built-in cross profile intent filters.
+                Intent intent = new Intent(
+                        DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_CHANGED);
+                intent.setPackage(MANAGED_PROVISIONING_PKG);
+                intent.putExtra(Intent.EXTRA_USER_ID, userId);
+                intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+                mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
+            }
+        }
+    }
+
     static class ActiveAdmin {
         private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
         private static final String TAG_TEST_ONLY_ADMIN = "test-only-admin";
@@ -1560,6 +1590,7 @@
                             removedAdmin = true;
                             policy.mAdminList.remove(i);
                             policy.mAdminMap.remove(aa.info.getComponent());
+                            pushActiveAdminPackagesLocked(userHandle);
                         }
                     }
                 } catch (RemoteException re) {
@@ -1653,6 +1684,10 @@
             return LocalServices.getService(PackageManagerInternal.class);
         }
 
+        UsageStatsManagerInternal getUsageStatsManagerInternal() {
+            return LocalServices.getService(UsageStatsManagerInternal.class);
+        }
+
         NotificationManager getNotificationManager() {
             return mContext.getSystemService(NotificationManager.class);
         }
@@ -1691,6 +1726,10 @@
             return ActivityManager.getService();
         }
 
+        ActivityManagerInternal getActivityManagerInternal() {
+            return LocalServices.getService(ActivityManagerInternal.class);
+        }
+
         IPackageManager getIPackageManager() {
             return AppGlobals.getPackageManager();
         }
@@ -1923,6 +1962,8 @@
 
         mUserManager = Preconditions.checkNotNull(injector.getUserManager());
         mUserManagerInternal = Preconditions.checkNotNull(injector.getUserManagerInternal());
+        mUsageStatsManagerInternal = Preconditions.checkNotNull(
+                injector.getUsageStatsManagerInternal());
         mIPackageManager = Preconditions.checkNotNull(injector.getIPackageManager());
         mTelephonyManager = Preconditions.checkNotNull(injector.getTelephonyManager());
 
@@ -1972,6 +2013,8 @@
         LocalServices.addService(DevicePolicyManagerInternal.class, mLocalService);
 
         mSetupContentObserver = new SetupContentObserver(mHandler);
+
+        mUserManagerInternal.addUserRestrictionsListener(new RestrictionsListener(mContext));
     }
 
     /**
@@ -3190,6 +3233,11 @@
     }
 
     @Override
+    void handleStart() {
+        pushActiveAdminPackages();
+    }
+
+    @Override
     void handleStartUser(int userId) {
         updateScreenCaptureDisabledInWindowManager(userId,
                 getScreenCaptureDisabled(null, userId));
@@ -3357,6 +3405,8 @@
                 if (replaceIndex == -1) {
                     policy.mAdminList.add(newAdmin);
                     enableIfNecessary(info.getPackageName(), userHandle);
+                    mUsageStatsManagerInternal.onActiveAdminAdded(
+                            adminReceiver.getPackageName(), userHandle);
                 } else {
                     policy.mAdminList.set(replaceIndex, newAdmin);
                 }
@@ -3369,6 +3419,35 @@
         }
     }
 
+    private void pushActiveAdminPackages() {
+        synchronized (this) {
+            final List<UserInfo> users = mUserManager.getUsers();
+            for (int i = users.size() - 1; i >= 0; --i) {
+                final int userId = users.get(i).id;
+                mUsageStatsManagerInternal.setActiveAdminApps(
+                        getActiveAdminPackagesLocked(userId), userId);
+            }
+        }
+    }
+
+    private void pushActiveAdminPackagesLocked(int userId) {
+        mUsageStatsManagerInternal.setActiveAdminApps(
+                getActiveAdminPackagesLocked(userId), userId);
+    }
+
+    private Set<String> getActiveAdminPackagesLocked(int userId) {
+        final DevicePolicyData policy = getUserData(userId);
+        Set<String> adminPkgs = null;
+        for (int i = policy.mAdminList.size() - 1; i >= 0; --i) {
+            final String pkgName = policy.mAdminList.get(i).info.getPackageName();
+            if (adminPkgs == null) {
+                adminPkgs = new ArraySet<>();
+            }
+            adminPkgs.add(pkgName);
+        }
+        return adminPkgs;
+    }
+
     private void transferActiveAdminUncheckedLocked(ComponentName incomingReceiver,
             ComponentName outgoingReceiver, int userHandle) {
         final DevicePolicyData policy = getUserData(userHandle);
@@ -3487,6 +3566,7 @@
         }
     }
 
+    @Override
     public void forceRemoveActiveAdmin(ComponentName adminReceiver, int userHandle) {
         if (!mHasFeature) {
             return;
@@ -3540,7 +3620,7 @@
     private boolean isPackageTestOnly(String packageName, int userHandle) {
         final ApplicationInfo ai;
         try {
-            ai = mIPackageManager.getApplicationInfo(packageName,
+            ai = mInjector.getIPackageManager().getApplicationInfo(packageName,
                     (PackageManager.MATCH_DIRECT_BOOT_AWARE
                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE), userHandle);
         } catch (RemoteException e) {
@@ -3562,7 +3642,7 @@
     }
 
     private void enforceShell(String method) {
-        final int callingUid = Binder.getCallingUid();
+        final int callingUid = mInjector.binderGetCallingUid();
         if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
             throw new SecurityException("Non-shell user attempted to call " + method);
         }
@@ -8564,14 +8644,6 @@
                         Settings.Secure.USER_SETUP_COMPLETE, 1, userHandle);
             }
 
-            if ((flags & START_USER_IN_BACKGROUND) != 0) {
-                try {
-                    mInjector.getIActivityManager().startUserInBackground(userHandle);
-                } catch (RemoteException re) {
-                    // Does not happen, same process
-                }
-            }
-
             return user;
         } catch (Throwable re) {
             mUserManager.removeUser(userHandle);
@@ -8584,6 +8656,8 @@
     @Override
     public boolean removeUser(ComponentName who, UserHandle userHandle) {
         Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkNotNull(userHandle, "UserHandle is null");
+
         synchronized (this) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
         }
@@ -8622,6 +8696,7 @@
     @Override
     public boolean switchUser(ComponentName who, UserHandle userHandle) {
         Preconditions.checkNotNull(who, "ComponentName is null");
+
         synchronized (this) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
 
@@ -8642,8 +8717,40 @@
     }
 
     @Override
+    public boolean startUserInBackground(ComponentName who, UserHandle userHandle) {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkNotNull(userHandle, "UserHandle is null");
+
+        synchronized (this) {
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        }
+
+        final int userId = userHandle.getIdentifier();
+        if (isManagedProfile(userId)) {
+            Log.w(LOG_TAG, "Managed profile cannot be started in background");
+            return false;
+        }
+
+        final long id = mInjector.binderClearCallingIdentity();
+        try {
+            if (!mInjector.getActivityManagerInternal().canStartMoreUsers()) {
+                Log.w(LOG_TAG, "Cannot start more users in background");
+                return false;
+            }
+
+            return mInjector.getIActivityManager().startUserInBackground(userId);
+        } catch (RemoteException e) {
+            // Same process, should not happen.
+            return false;
+        } finally {
+            mInjector.binderRestoreCallingIdentity(id);
+        }
+    }
+
+    @Override
     public boolean stopUser(ComponentName who, UserHandle userHandle) {
         Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkNotNull(userHandle, "UserHandle is null");
 
         synchronized (this) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
@@ -9692,7 +9799,7 @@
                 return false;
             }
             mLockPatternUtils.setLockScreenDisabled(disabled, userId);
-            mInjector.getIWindowManager().dismissKeyguard(null);
+            mInjector.getIWindowManager().dismissKeyguard(null /* callback */, null /* message */);
         } catch (RemoteException e) {
             // Same process, does not happen.
         } finally {
@@ -11163,6 +11270,7 @@
             if (doProxyCleanup) {
                 resetGlobalProxyLocked(policy);
             }
+            pushActiveAdminPackagesLocked(userHandle);
             saveSettingsLocked(userHandle);
             updateMaximumTimeToLockLocked(userHandle);
             policy.mRemovingAdmins.remove(adminReceiver);
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index f9ebd28..bf58224 100644
--- a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -16,7 +16,11 @@
 
 package com.android.server.backup;
 
-import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES;
+import static com.android.server.backup.testing.TransportData.backupTransport;
+import static com.android.server.backup.testing.TransportData.d2dTransport;
+import static com.android.server.backup.testing.TransportData.localTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpCurrentTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpTransports;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -24,6 +28,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.robolectric.Shadows.shadowOf;
@@ -39,8 +44,8 @@
 import android.provider.Settings;
 
 import com.android.server.backup.testing.ShadowAppBackupUtils;
-import com.android.server.backup.testing.TransportTestUtils;
-import com.android.server.backup.testing.TransportTestUtils.TransportData;
+import com.android.server.backup.testing.TransportData;
+import com.android.server.backup.testing.TransportTestUtils.TransportMock;
 import com.android.server.backup.transport.TransportNotRegisteredException;
 import com.android.server.testing.FrameworkRobolectricTestRunner;
 import com.android.server.testing.SystemLoaderClasses;
@@ -57,10 +62,10 @@
 import org.robolectric.shadows.ShadowLog;
 import org.robolectric.shadows.ShadowLooper;
 import org.robolectric.shadows.ShadowSettings;
+import org.robolectric.shadows.ShadowSystemClock;
 
 import java.io.File;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 @RunWith(FrameworkRobolectricTestRunner.class)
@@ -73,22 +78,24 @@
 @Presubmit
 public class BackupManagerServiceRoboTest {
     private static final String TAG = "BMSTest";
-    private static final String TRANSPORT_NAME =
-            "com.google.android.gms/.backup.BackupTransportService";
 
     @Mock private TransportManager mTransportManager;
     private HandlerThread mBackupThread;
     private ShadowLooper mShadowBackupLooper;
     private File mBaseStateDir;
     private File mDataDir;
-    private RefactoredBackupManagerService mBackupManagerService;
     private ShadowContextWrapper mShadowContext;
     private Context mContext;
+    private TransportData mTransport;
+    private String mTransportName;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        mTransport = backupTransport();
+        mTransportName = mTransport.transportName;
+
         mBackupThread = new HandlerThread("backup-test");
         mBackupThread.setUncaughtExceptionHandler(
                 (t, e) -> ShadowLog.e(TAG, "Uncaught exception in test thread " + t.getName(), e));
@@ -102,15 +109,6 @@
         File cacheDir = mContext.getCacheDir();
         mBaseStateDir = new File(cacheDir, "base_state_dir");
         mDataDir = new File(cacheDir, "data_dir");
-
-        mBackupManagerService =
-                new RefactoredBackupManagerService(
-                        mContext,
-                        new Trampoline(mContext),
-                        mBackupThread,
-                        mBaseStateDir,
-                        mDataDir,
-                        mTransportManager);
     }
 
     @After
@@ -124,10 +122,12 @@
     @Test
     public void testDestinationString() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
-        when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+        when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName)))
                 .thenReturn("destinationString");
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
-        String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME);
+        String destination = backupManagerService.getDestinationString(mTransportName);
 
         assertThat(destination).isEqualTo("destinationString");
     }
@@ -135,10 +135,12 @@
     @Test
     public void testDestinationString_whenTransportNotRegistered() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
-        when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+        when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName)))
                 .thenThrow(TransportNotRegisteredException.class);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
-        String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME);
+        String destination = backupManagerService.getDestinationString(mTransportName);
 
         assertThat(destination).isNull();
     }
@@ -146,12 +148,14 @@
     @Test
     public void testDestinationString_withoutPermission() throws Exception {
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
-        when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+        when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName)))
                 .thenThrow(TransportNotRegisteredException.class);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
         expectThrows(
                 SecurityException.class,
-                () -> mBackupManagerService.getDestinationString(TRANSPORT_NAME));
+                () -> backupManagerService.getDestinationString(mTransportName));
     }
 
     /* Tests for app eligibility */
@@ -159,24 +163,28 @@
     @Test
     public void testIsAppEligibleForBackup_whenAppEligible() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
-        TransportData transport =
-                TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+        TransportMock transportMock = setUpCurrentTransport(mTransportManager, backupTransport());
         ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> true;
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
-        boolean result = mBackupManagerService.isAppEligibleForBackup("app.package");
+        boolean result = backupManagerService.isAppEligibleForBackup("app.package");
 
         assertThat(result).isTrue();
+
         verify(mTransportManager)
-                .disposeOfTransportClient(eq(transport.transportClientMock), any());
+                .disposeOfTransportClient(eq(transportMock.transportClient), any());
     }
 
     @Test
     public void testIsAppEligibleForBackup_whenAppNotEligible() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
-        TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+        setUpCurrentTransport(mTransportManager, mTransport);
         ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false;
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
-        boolean result = mBackupManagerService.isAppEligibleForBackup("app.package");
+        boolean result = backupManagerService.isAppEligibleForBackup("app.package");
 
         assertThat(result).isFalse();
     }
@@ -184,38 +192,43 @@
     @Test
     public void testIsAppEligibleForBackup_withoutPermission() throws Exception {
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
-        TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+        setUpCurrentTransport(mTransportManager, mTransport);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
         expectThrows(
                 SecurityException.class,
-                () -> mBackupManagerService.isAppEligibleForBackup("app.package"));
+                () -> backupManagerService.isAppEligibleForBackup("app.package"));
     }
 
     @Test
     public void testFilterAppsEligibleForBackup() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
-        TransportData transport =
-                TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+        TransportMock transportMock = setUpCurrentTransport(mTransportManager, mTransport);
         Map<String, Boolean> packagesMap = new HashMap<>();
         packagesMap.put("package.a", true);
         packagesMap.put("package.b", false);
         ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = packagesMap::get;
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
         String[] packages = packagesMap.keySet().toArray(new String[packagesMap.size()]);
 
-        String[] filtered = mBackupManagerService.filterAppsEligibleForBackup(packages);
+        String[] filtered = backupManagerService.filterAppsEligibleForBackup(packages);
 
         assertThat(filtered).asList().containsExactly("package.a");
         verify(mTransportManager)
-                .disposeOfTransportClient(eq(transport.transportClientMock), any());
+                .disposeOfTransportClient(eq(transportMock.transportClient), any());
     }
 
     @Test
     public void testFilterAppsEligibleForBackup_whenNoneIsEligible() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
         ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false;
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
         String[] filtered =
-                mBackupManagerService.filterAppsEligibleForBackup(
+                backupManagerService.filterAppsEligibleForBackup(
                         new String[] {"package.a", "package.b"});
 
         assertThat(filtered).isEmpty();
@@ -224,12 +237,14 @@
     @Test
     public void testFilterAppsEligibleForBackup_withoutPermission() throws Exception {
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
-        TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+        setUpCurrentTransport(mTransportManager, mTransport);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
         expectThrows(
                 SecurityException.class,
                 () ->
-                        mBackupManagerService.filterAppsEligibleForBackup(
+                        backupManagerService.filterAppsEligibleForBackup(
                                 new String[] {"package.a", "package.b"}));
     }
 
@@ -238,14 +253,12 @@
     private TransportData mNewTransport;
     private TransportData mOldTransport;
     private ComponentName mNewTransportComponent;
-    private ISelectBackupTransportCallback mCallback;
 
     private void setUpForSelectTransport() throws Exception {
-        List<TransportData> transports =
-                TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES);
-        mNewTransport = transports.get(0);
-        mNewTransportComponent = mNewTransport.transportClientMock.getTransportComponent();
-        mOldTransport = transports.get(1);
+        mNewTransport = backupTransport();
+        mNewTransportComponent = mNewTransport.getTransportComponent();
+        mOldTransport = d2dTransport();
+        setUpTransports(mTransportManager, mNewTransport, mOldTransport, localTransport());
         when(mTransportManager.selectTransport(eq(mNewTransport.transportName)))
                 .thenReturn(mOldTransport.transportName);
     }
@@ -254,9 +267,11 @@
     public void testSelectBackupTransport() throws Exception {
         setUpForSelectTransport();
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
         String oldTransport =
-                mBackupManagerService.selectBackupTransport(mNewTransport.transportName);
+                backupManagerService.selectBackupTransport(mNewTransport.transportName);
 
         assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName);
         assertThat(oldTransport).isEqualTo(mOldTransport.transportName);
@@ -266,10 +281,12 @@
     public void testSelectBackupTransport_withoutPermission() throws Exception {
         setUpForSelectTransport();
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
         expectThrows(
                 SecurityException.class,
-                () -> mBackupManagerService.selectBackupTransport(mNewTransport.transportName));
+                () -> backupManagerService.selectBackupTransport(mNewTransport.transportName));
     }
 
     @Test
@@ -278,9 +295,11 @@
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
         when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent)))
                 .thenReturn(BackupManager.SUCCESS);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
         ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
 
-        mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
+        backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
 
         mShadowBackupLooper.runToEndOfTasks();
         assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName);
@@ -293,9 +312,11 @@
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
         when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent)))
                 .thenReturn(BackupManager.ERROR_TRANSPORT_UNAVAILABLE);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
         ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
 
-        mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
+        backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
 
         mShadowBackupLooper.runToEndOfTasks();
         assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName);
@@ -304,19 +325,19 @@
 
     @Test
     public void testSelectBackupTransportAsync_whenTransportGetsUnregistered() throws Exception {
-        TransportTestUtils.setUpTransports(
-                mTransportManager, new TransportData(TRANSPORT_NAME, null, null));
-        ComponentName newTransportComponent =
-                TransportTestUtils.transportComponentName(TRANSPORT_NAME);
+        setUpTransports(mTransportManager, mTransport.unregistered());
+        ComponentName newTransportComponent = mTransport.getTransportComponent();
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
         when(mTransportManager.registerAndSelectTransport(eq(newTransportComponent)))
                 .thenReturn(BackupManager.SUCCESS);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
         ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
 
-        mBackupManagerService.selectBackupTransportAsync(newTransportComponent, callback);
+        backupManagerService.selectBackupTransportAsync(newTransportComponent, callback);
 
         mShadowBackupLooper.runToEndOfTasks();
-        assertThat(getSettingsTransport()).isNotEqualTo(TRANSPORT_NAME);
+        assertThat(getSettingsTransport()).isNotEqualTo(mTransportName);
         verify(callback).onFailure(anyInt());
     }
 
@@ -324,13 +345,14 @@
     public void testSelectBackupTransportAsync_withoutPermission() throws Exception {
         setUpForSelectTransport();
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
-        ComponentName newTransportComponent =
-                mNewTransport.transportClientMock.getTransportComponent();
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
+        ComponentName newTransportComponent = mNewTransport.getTransportComponent();
 
         expectThrows(
                 SecurityException.class,
                 () ->
-                        mBackupManagerService.selectBackupTransportAsync(
+                        backupManagerService.selectBackupTransportAsync(
                                 newTransportComponent, mock(ISelectBackupTransportCallback.class)));
     }
 
@@ -338,4 +360,55 @@
         return ShadowSettings.ShadowSecure.getString(
                 mContext.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
     }
+
+    /* Miscellaneous tests */
+
+    @Test
+    public void testConstructor_postRegisterTransports() {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+
+        createBackupManagerService();
+
+        mShadowBackupLooper.runToEndOfTasks();
+        verify(mTransportManager).registerTransports();
+    }
+
+    @Test
+    public void testConstructor_doesNotRegisterTransportsSynchronously() {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+
+        createBackupManagerService();
+
+        // Operations posted to mBackupThread only run with mShadowBackupLooper.runToEndOfTasks()
+        verify(mTransportManager, never()).registerTransports();
+    }
+
+    private RefactoredBackupManagerService createBackupManagerService() {
+        return new RefactoredBackupManagerService(
+                mContext,
+                new Trampoline(mContext),
+                mBackupThread,
+                mBaseStateDir,
+                mDataDir,
+                mTransportManager);
+    }
+
+    private RefactoredBackupManagerService createInitializedBackupManagerService() {
+        RefactoredBackupManagerService backupManagerService =
+                new RefactoredBackupManagerService(
+                        mContext,
+                        new Trampoline(mContext),
+                        mBackupThread,
+                        mBaseStateDir,
+                        mDataDir,
+                        mTransportManager);
+        mShadowBackupLooper.runToEndOfTasks();
+        // Handler instances have their own clock, so advancing looper (with runToEndOfTasks())
+        // above does NOT advance the handlers' clock, hence whenever a handler post messages with
+        // specific time to the looper the time of those messages will be before the looper's time.
+        // To fix this we advance SystemClock as well since that is from where the handlers read
+        // time.
+        ShadowSystemClock.setCurrentTimeMillis(mShadowBackupLooper.getScheduler().getCurrentTime());
+        return backupManagerService;
+    }
 }
diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
index acd670f..cf0bc23 100644
--- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
@@ -16,108 +16,97 @@
 
 package com.android.server.backup;
 
+import static com.android.server.backup.testing.TransportData.genericTransport;
+import static com.android.server.backup.testing.TransportTestUtils.mockTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpTransportsForTransportManager;
+
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.mock;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+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 static org.robolectric.shadow.api.Shadow.extract;
 import static org.testng.Assert.expectThrows;
 
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static java.util.stream.Stream.concat;
+
 import android.annotation.Nullable;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.IBinder;
-import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
-import com.android.internal.backup.IBackupTransport;
-import com.android.server.backup.testing.ShadowBackupTransportStub;
 import com.android.server.backup.testing.ShadowContextImplForBackup;
-import com.android.server.backup.testing.ShadowPackageManagerForBackup;
-import com.android.server.backup.testing.TransportBoundListenerStub;
+import com.android.server.testing.shadows.FrameworkShadowPackageManager;
+import com.android.server.backup.testing.TransportData;
+import com.android.server.backup.testing.TransportTestUtils.TransportMock;
+import com.android.server.backup.transport.OnTransportRegisteredListener;
 import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportClientManager;
 import com.android.server.backup.transport.TransportNotRegisteredException;
 import com.android.server.testing.FrameworkRobolectricTestRunner;
 import com.android.server.testing.SystemLoaderClasses;
+import com.android.server.testing.shadows.FrameworkShadowContextImpl;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowLog;
-import org.robolectric.shadows.ShadowLooper;
 import org.robolectric.shadows.ShadowPackageManager;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Stream;
 
 @RunWith(FrameworkRobolectricTestRunner.class)
 @Config(
-        manifest = Config.NONE,
-        sdk = 26,
-        shadows = {
-                ShadowContextImplForBackup.class,
-                ShadowBackupTransportStub.class,
-                ShadowPackageManagerForBackup.class
-        }
+    manifest = Config.NONE,
+    sdk = 26,
+    shadows = {FrameworkShadowPackageManager.class, FrameworkShadowContextImpl.class}
 )
 @SystemLoaderClasses({TransportManager.class})
 @Presubmit
 public class TransportManagerTest {
-    private static final String PACKAGE_NAME = "some.package.name";
-    private static final String ANOTHER_PACKAGE_NAME = "another.package.name";
+    private static final String PACKAGE_A = "some.package.a";
+    private static final String PACKAGE_B = "some.package.b";
 
-    private TransportInfo mTransport1;
-    private TransportInfo mTransport2;
+    @Mock private OnTransportRegisteredListener mListener;
+    @Mock private TransportClientManager mTransportClientManager;
+    private TransportData mTransportA1;
+    private TransportData mTransportA2;
+    private TransportData mTransportB1;
 
-    private ShadowPackageManager mPackageManagerShadow;
-
-    private final TransportBoundListenerStub mTransportBoundListenerStub =
-            new TransportBoundListenerStub(true);
+    private ShadowPackageManager mShadowPackageManager;
+    private Context mContext;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        ShadowLog.stream = System.out;
-
-        mPackageManagerShadow =
-                (ShadowPackageManagerForBackup)
+        mShadowPackageManager =
+                (FrameworkShadowPackageManager)
                         extract(RuntimeEnvironment.application.getPackageManager());
+        mContext = RuntimeEnvironment.application.getApplicationContext();
 
-        mTransport1 = new TransportInfo(
-                PACKAGE_NAME,
-                "transport1.name",
-                new Intent(),
-                "currentDestinationString",
-                new Intent(),
-                "dataManagementLabel");
-        mTransport2 = new TransportInfo(
-                PACKAGE_NAME,
-                "transport2.name",
-                new Intent(),
-                "currentDestinationString",
-                new Intent(),
-                "dataManagementLabel");
-
-        ShadowContextImplForBackup.sComponentBinderMap.put(mTransport1.componentName,
-                mTransport1.binder);
-        ShadowContextImplForBackup.sComponentBinderMap.put(mTransport2.componentName,
-                mTransport2.binder);
-        ShadowBackupTransportStub.sBinderTransportMap.put(
-                mTransport1.binder, mTransport1.binderInterface);
-        ShadowBackupTransportStub.sBinderTransportMap.put(
-                mTransport2.binder, mTransport2.binderInterface);
+        mTransportA1 = genericTransport(PACKAGE_A, "TransportFoo");
+        mTransportA2 = genericTransport(PACKAGE_A, "TransportBar");
+        mTransportB1 = genericTransport(PACKAGE_B, "TransportBaz");
     }
 
     @After
@@ -126,560 +115,441 @@
     }
 
     @Test
-    public void onPackageAdded_bindsToAllTransports() throws Exception {
-        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                new HashSet<>(Arrays.asList(
-                        mTransport1.componentName, mTransport2.componentName)),
-                null /* defaultTransport */,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-        transportManager.onPackageAdded(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.name, mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isTrue();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isTrue();
-    }
-
-    @Test
-    public void onPackageAdded_oneTransportUnavailable_bindsToOnlyOneTransport() throws Exception {
-        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
-        ShadowContextImplForBackup.sUnbindableComponents.add(mTransport1.componentName);
-
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                new HashSet<>(Arrays.asList(
-                        mTransport1.componentName, mTransport2.componentName)),
-                null /* defaultTransport */,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-        transportManager.onPackageAdded(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Collections.singleton(mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Collections.singleton(mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isFalse();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isTrue();
-    }
-
-    @Test
-    public void onPackageAdded_whitelistIsNull_doesNotBindToTransports() throws Exception {
-        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                null /* whitelist */,
-                null /* defaultTransport */,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-        transportManager.onPackageAdded(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).isEmpty();
-        assertThat(transportManager.getBoundTransportNames()).isEmpty();
-        assertThat(mTransportBoundListenerStub.isCalled()).isFalse();
-    }
-
-    @Test
-    public void onPackageAdded_onlyOneTransportWhitelisted_onlyConnectsToWhitelistedTransport()
-            throws Exception {
-        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                new HashSet<>(Collections.singleton(mTransport2.componentName)),
-                null /* defaultTransport */,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-        transportManager.onPackageAdded(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Collections.singleton(mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Collections.singleton(mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isFalse();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isTrue();
-    }
-
-    @Test
-    public void onPackageAdded_appIsNotPrivileged_doesNotBindToTransports() throws Exception {
-        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), 0);
-
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                new HashSet<>(Arrays.asList(
-                        mTransport1.componentName, mTransport2.componentName)),
-                null /* defaultTransport */,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-        transportManager.onPackageAdded(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).isEmpty();
-        assertThat(transportManager.getBoundTransportNames()).isEmpty();
-        assertThat(mTransportBoundListenerStub.isCalled()).isFalse();
-    }
-
-    @Test
-    public void onPackageRemoved_transportsUnbound() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        transportManager.onPackageRemoved(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).isEmpty();
-        assertThat(transportManager.getBoundTransportNames()).isEmpty();
-    }
-
-    @Test
-    public void onPackageRemoved_incorrectPackageName_nothingHappens() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        transportManager.onPackageRemoved(ANOTHER_PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.name, mTransport2.name));
-    }
-
-    @Test
-    public void onPackageChanged_oneComponentChanged_onlyOneTransportRebound() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name});
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.name, mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isFalse();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isTrue();
-    }
-
-    @Test
-    public void onPackageChanged_nothingChanged_noTransportsRebound() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        transportManager.onPackageChanged(PACKAGE_NAME, new String[0]);
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.name, mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isFalse();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isFalse();
-    }
-
-    @Test
-    public void onPackageChanged_unexpectedComponentChanged_noTransportsRebound() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        transportManager.onPackageChanged(PACKAGE_NAME, new String[]{"unexpected.component"});
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.name, mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isFalse();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isFalse();
-    }
-
-    @Test
-    public void onPackageChanged_transportsRebound() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name});
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.name, mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isFalse();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isTrue();
-    }
-
-    @Test
-    public void getTransportBinder_returnsCorrectBinder() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        assertThat(transportManager.getTransportBinder(mTransport1.name)).isEqualTo(
-                mTransport1.binderInterface);
-        assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo(
-                mTransport2.binderInterface);
-    }
-
-    @Test
-    public void getTransportBinder_incorrectTransportName_returnsNull() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        assertThat(transportManager.getTransportBinder("incorrect.transport")).isNull();
-    }
-
-    @Test
-    public void getTransportBinder_oneTransportUnavailable_returnsCorrectBinder() throws Exception {
+    public void testRegisterTransports() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2, mTransportB1);
         TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2),
-                        Collections.singletonList(mTransport1), mTransport1.name);
+                createTransportManager(mTransportA1, mTransportA2, mTransportB1);
 
-        assertThat(transportManager.getTransportBinder(mTransport1.name)).isNull();
-        assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo(
-                mTransport2.binderInterface);
+        transportManager.registerTransports();
+
+        assertRegisteredTransports(
+                transportManager, asList(mTransportA1, mTransportA2, mTransportB1));
+
+        verify(mListener)
+                .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
+        verify(mListener)
+                .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName);
+        verify(mListener)
+                .onTransportRegistered(mTransportB1.transportName, mTransportB1.transportDirName);
     }
 
     @Test
-    public void getCurrentTransport_selectTransportNotCalled_returnsDefaultTransport()
+    public void
+            testRegisterTransports_whenOneTransportUnavailable_doesNotRegisterUnavailableTransport()
+                    throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        TransportData transport1 = mTransportA1.unavailable();
+        TransportData transport2 = mTransportA2;
+        setUpTransports(transport1, transport2);
+        TransportManager transportManager = createTransportManager(transport1, transport2);
+
+        transportManager.registerTransports();
+
+        assertRegisteredTransports(transportManager, singletonList(transport2));
+        verify(mListener, never())
+                .onTransportRegistered(transport1.transportName, transport1.transportDirName);
+        verify(mListener)
+                .onTransportRegistered(transport2.transportName, transport2.transportDirName);
+    }
+
+    @Test
+    public void testRegisterTransports_whenWhitelistIsEmpty_doesNotRegisterTransports()
             throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(null);
 
-        assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name);
+        transportManager.registerTransports();
+
+        assertRegisteredTransports(transportManager, emptyList());
+        verify(mListener, never()).onTransportRegistered(any(), any());
     }
 
     @Test
-    public void getCurrentTransport_selectTransportCalled_returnsCorrectTransport()
+    public void
+            testRegisterTransports_whenOnlyOneTransportWhitelisted_onlyRegistersWhitelistedTransport()
+                    throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(null, mTransportA1);
+
+        transportManager.registerTransports();
+
+        assertRegisteredTransports(transportManager, singletonList(mTransportA1));
+        verify(mListener)
+                .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
+        verify(mListener, never())
+                .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName);
+    }
+
+    @Test
+    public void testRegisterTransports_whenAppIsNotPrivileged_doesNotRegisterTransports()
             throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name);
-
-        transportManager.selectTransport(mTransport2.name);
-
-        assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport2.name);
-    }
-
-    @Test
-    public void getCurrentTransportBinder_returnsCorrectBinder() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        assertThat(transportManager.getCurrentTransportBinder())
-                .isEqualTo(mTransport1.binderInterface);
-    }
-
-    @Test
-    public void getCurrentTransportBinder_transportNotBound_returnsNull() throws Exception {
+        // Note ApplicationInfo.PRIVATE_FLAG_PRIVILEGED is missing from flags
+        setUpPackage(PACKAGE_A, 0);
+        setUpTransports(mTransportA1, mTransportA2);
         TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2),
-                        Collections.singletonList(mTransport1), mTransport2.name);
+                createTransportManager(null, mTransportA1, mTransportA2);
 
-        transportManager.selectTransport(mTransport1.name);
+        transportManager.registerTransports();
 
-        assertThat(transportManager.getCurrentTransportBinder()).isNull();
+        assertRegisteredTransports(transportManager, emptyList());
+        verify(mListener, never()).onTransportRegistered(any(), any());
     }
 
     @Test
-    public void getTransportName_returnsCorrectTransportName() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+    public void testOnPackageAdded_registerTransports() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1);
 
-        assertThat(transportManager.getTransportName(mTransport1.binderInterface))
-                .isEqualTo(mTransport1.name);
-        assertThat(transportManager.getTransportName(mTransport2.binderInterface))
-                .isEqualTo(mTransport2.name);
+        transportManager.onPackageAdded(PACKAGE_A);
+
+        assertRegisteredTransports(transportManager, asList(mTransportA1));
+        verify(mListener)
+                .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
     }
 
     @Test
-    public void getTransportName_transportNotBound_returnsNull() throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2),
-                        Collections.singletonList(mTransport1), mTransport1.name);
+    public void testOnPackageRemoved_unregisterTransports() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportB1);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportB1);
+        transportManager.registerTransports();
 
-        assertThat(transportManager.getTransportName(mTransport1.binderInterface)).isNull();
-        assertThat(transportManager.getTransportName(mTransport2.binderInterface))
-                .isEqualTo(mTransport2.name);
+        transportManager.onPackageRemoved(PACKAGE_A);
+
+        assertRegisteredTransports(transportManager, singletonList(mTransportB1));
     }
 
     @Test
-    public void getTransportWhitelist_returnsCorrectWhiteList() throws Exception {
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                new HashSet<>(Arrays.asList(mTransport1.componentName, mTransport2.componentName)),
-                mTransport1.name,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
+    public void testOnPackageRemoved_whenUnknownPackage_nothingHappens() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1);
+        transportManager.registerTransports();
 
-        assertThat(transportManager.getTransportWhitelist()).containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
+        transportManager.onPackageRemoved(PACKAGE_A + "unknown");
+
+        assertRegisteredTransports(transportManager, singletonList(mTransportA1));
     }
 
     @Test
-    public void getTransportWhitelist_whiteListIsNull_returnsEmptyArray() throws Exception {
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                null /* whitelist */,
-                mTransport1.name,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-
-        assertThat(transportManager.getTransportWhitelist()).isEmpty();
-    }
-
-    @Test
-    public void selectTransport_setsTransportCorrectlyAndReturnsPreviousTransport()
+    public void testOnPackageChanged_whenOneComponentChanged_onlyOneTransportReRegistered()
             throws Exception {
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                null /* whitelist */,
-                mTransport1.name,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
+        // Reset listener to verify calls after registerTransports() above
+        reset(mListener);
 
-        assertThat(transportManager.selectTransport(mTransport2.name)).isEqualTo(mTransport1.name);
-        assertThat(transportManager.selectTransport(mTransport1.name)).isEqualTo(mTransport2.name);
+        transportManager.onPackageChanged(
+                PACKAGE_A, mTransportA1.getTransportComponent().getClassName());
+
+        assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2));
+        verify(mListener)
+                .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
+        verify(mListener, never())
+                .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName);
     }
 
     @Test
-    public void getTransportClient_forRegisteredTransport_returnCorrectly() throws Exception {
+    public void testOnPackageChanged_whenNoComponentsChanged_doesNotRegisterTransports()
+            throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1);
+        transportManager.registerTransports();
+        reset(mListener);
+
+        transportManager.onPackageChanged(PACKAGE_A);
+
+        assertRegisteredTransports(transportManager, singletonList(mTransportA1));
+        verify(mListener, never()).onTransportRegistered(any(), any());
+    }
+
+    @Test
+    public void testOnPackageChanged_whenUnknownComponentChanged_noTransportsRegistered()
+            throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1);
+        transportManager.registerTransports();
+        reset(mListener);
+
+        transportManager.onPackageChanged(PACKAGE_A, PACKAGE_A + ".UnknownComponent");
+
+        assertRegisteredTransports(transportManager, singletonList(mTransportA1));
+        verify(mListener, never()).onTransportRegistered(any(), any());
+    }
+
+    @Test
+    public void testOnPackageChanged_reRegisterTransports() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
+        reset(mListener);
+
+        transportManager.onPackageChanged(
+                PACKAGE_A,
+                mTransportA1.getTransportComponent().getClassName(),
+                mTransportA2.getTransportComponent().getClassName());
+
+        assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2));
+        verify(mListener)
+                .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
+        verify(mListener)
+                .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName);
+    }
+
+    @Test
+    public void testGetCurrentTransportName_whenSelectTransportNotCalled_returnsDefaultTransport()
+            throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
+
+        String currentTransportName = transportManager.getCurrentTransportName();
+
+        assertThat(currentTransportName).isEqualTo(mTransportA1.transportName);
+    }
+
+    @Test
+    public void testGetCurrentTransport_whenSelectTransportCalled_returnsSelectedTransport()
+            throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
+        transportManager.selectTransport(mTransportA2.transportName);
+
+        String currentTransportName = transportManager.getCurrentTransportName();
+
+        assertThat(currentTransportName).isEqualTo(mTransportA2.transportName);
+    }
+
+    @Test
+    public void testGetTransportWhitelist() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+
+        Set<ComponentName> transportWhitelist = transportManager.getTransportWhitelist();
+
+        assertThat(transportWhitelist)
+                .containsExactlyElementsIn(
+                        asList(
+                                mTransportA1.getTransportComponent(),
+                                mTransportA2.getTransportComponent()));
+    }
+
+    @Test
+    public void testSelectTransport() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
         TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+                createTransportManager(null, mTransportA1, mTransportA2);
+
+        String transport1 = transportManager.selectTransport(mTransportA1.transportName);
+        String transport2 = transportManager.selectTransport(mTransportA2.transportName);
+
+        assertThat(transport1).isNull();
+        assertThat(transport2).isEqualTo(mTransportA1.transportName);
+    }
+
+    @Test
+    public void testGetTransportClient_forRegisteredTransport() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
 
         TransportClient transportClient =
-                transportManager.getTransportClient(mTransport1.name, "caller");
+                transportManager.getTransportClient(mTransportA1.transportName, "caller");
 
-        assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName);
+        assertThat(transportClient.getTransportComponent())
+                .isEqualTo(mTransportA1.getTransportComponent());
     }
 
     @Test
-    public void getTransportClient_forOldNameOfTransportThatChangedName_returnsNull()
+    public void testGetTransportClient_forOldNameOfTransportThatChangedName_returnsNull()
             throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
         transportManager.updateTransportAttributes(
-                mTransport1.componentName, "newName", null, "destinationString", null, null);
+                mTransportA1.getTransportComponent(),
+                "newName",
+                null,
+                "destinationString",
+                null,
+                null);
 
         TransportClient transportClient =
-                transportManager.getTransportClient(mTransport1.name, "caller");
+                transportManager.getTransportClient(mTransportA1.transportName, "caller");
 
         assertThat(transportClient).isNull();
     }
 
     @Test
-    public void getTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly()
+    public void testGetTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly()
             throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
         transportManager.updateTransportAttributes(
-                mTransport1.componentName, "newName", null, "destinationString", null, null);
+                mTransportA1.getTransportComponent(),
+                "newName",
+                null,
+                "destinationString",
+                null,
+                null);
 
-        TransportClient transportClient =
-                transportManager.getTransportClient("newName", "caller");
+        TransportClient transportClient = transportManager.getTransportClient("newName", "caller");
 
-        assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName);
+        assertThat(transportClient.getTransportComponent())
+                .isEqualTo(mTransportA1.getTransportComponent());
     }
 
     @Test
-    public void getTransportName_forTransportThatChangedName_returnsNewName()
-            throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+    public void testGetTransportName_forTransportThatChangedName_returnsNewName() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
         transportManager.updateTransportAttributes(
-                mTransport1.componentName, "newName", null, "destinationString", null, null);
+                mTransportA1.getTransportComponent(),
+                "newName",
+                null,
+                "destinationString",
+                null,
+                null);
 
-        String transportName = transportManager.getTransportName(mTransport1.componentName);
+        String transportName =
+                transportManager.getTransportName(mTransportA1.getTransportComponent());
 
         assertThat(transportName).isEqualTo("newName");
     }
 
     @Test
-    public void isTransportRegistered_returnsCorrectly() throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Collections.singletonList(mTransport1),
-                        Collections.singletonList(mTransport2),
-                        mTransport1.name);
+    public void testIsTransportRegistered() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
 
-        assertThat(transportManager.isTransportRegistered(mTransport1.name)).isTrue();
-        assertThat(transportManager.isTransportRegistered(mTransport2.name)).isFalse();
+        boolean isTransportA1Registered =
+                transportManager.isTransportRegistered(mTransportA1.transportName);
+        boolean isTransportA2Registered =
+                transportManager.isTransportRegistered(mTransportA2.transportName);
+
+        assertThat(isTransportA1Registered).isTrue();
+        assertThat(isTransportA2Registered).isFalse();
     }
 
     @Test
-    public void getTransportAttributes_forRegisteredTransport_returnsCorrectValues()
+    public void testGetTransportAttributes_forRegisteredTransport_returnsCorrectValues()
             throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Collections.singletonList(mTransport1),
-                        mTransport1.name);
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1);
+        transportManager.registerTransports();
 
-        assertThat(transportManager.getTransportConfigurationIntent(mTransport1.name))
-                .isEqualTo(mTransport1.binderInterface.configurationIntent());
-        assertThat(transportManager.getTransportDataManagementIntent(mTransport1.name))
-                .isEqualTo(mTransport1.binderInterface.dataManagementIntent());
-        assertThat(transportManager.getTransportDataManagementLabel(mTransport1.name))
-                .isEqualTo(mTransport1.binderInterface.dataManagementLabel());
-        assertThat(transportManager.getTransportDirName(mTransport1.name))
-                .isEqualTo(mTransport1.binderInterface.transportDirName());
+        Intent configurationIntent =
+                transportManager.getTransportConfigurationIntent(mTransportA1.transportName);
+        Intent dataManagementIntent =
+                transportManager.getTransportDataManagementIntent(mTransportA1.transportName);
+        String dataManagementLabel =
+                transportManager.getTransportDataManagementLabel(mTransportA1.transportName);
+        String transportDirName = transportManager.getTransportDirName(mTransportA1.transportName);
+
+        assertThat(configurationIntent).isEqualTo(mTransportA1.configurationIntent);
+        assertThat(dataManagementIntent).isEqualTo(mTransportA1.dataManagementIntent);
+        assertThat(dataManagementLabel).isEqualTo(mTransportA1.dataManagementLabel);
+        assertThat(transportDirName).isEqualTo(mTransportA1.transportDirName);
     }
 
     @Test
-    public void getTransportAttributes_forUnregisteredTransport_throws()
-            throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Collections.singletonList(mTransport1),
-                        Collections.singletonList(mTransport2),
-                        mTransport1.name);
+    public void testGetTransportAttributes_forUnregisteredTransport_throws() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1);
+        transportManager.registerTransports();
 
         expectThrows(
                 TransportNotRegisteredException.class,
-                () -> transportManager.getTransportConfigurationIntent(mTransport2.name));
+                () -> transportManager.getTransportConfigurationIntent(mTransportA2.transportName));
         expectThrows(
                 TransportNotRegisteredException.class,
-                () -> transportManager.getTransportDataManagementIntent(
-                        mTransport2.name));
+                () ->
+                        transportManager.getTransportDataManagementIntent(
+                                mTransportA2.transportName));
         expectThrows(
                 TransportNotRegisteredException.class,
-                () -> transportManager.getTransportDataManagementLabel(mTransport2.name));
+                () -> transportManager.getTransportDataManagementLabel(mTransportA2.transportName));
         expectThrows(
                 TransportNotRegisteredException.class,
-                () -> transportManager.getTransportDirName(mTransport2.name));
+                () -> transportManager.getTransportDirName(mTransportA2.transportName));
     }
 
-    private void setUpPackageWithTransports(String packageName, List<TransportInfo> transports,
-            int flags) throws Exception {
+    private List<TransportMock> setUpTransports(TransportData... transports) throws Exception {
+        setUpTransportsForTransportManager(mShadowPackageManager, transports);
+        List<TransportMock> transportMocks = new ArrayList<>(transports.length);
+        for (TransportData transport : transports) {
+            TransportMock transportMock = mockTransport(transport);
+            when(mTransportClientManager.getTransportClient(
+                            eq(transport.getTransportComponent()), any()))
+                    .thenReturn(transportMock.transportClient);
+            transportMocks.add(transportMock);
+        }
+        return transportMocks;
+    }
+
+    private void setUpPackage(String packageName, int flags) {
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = packageName;
         packageInfo.applicationInfo = new ApplicationInfo();
         packageInfo.applicationInfo.privateFlags = flags;
-
-        mPackageManagerShadow.addPackage(packageInfo);
-
-        List<ResolveInfo> transportsInfo = new ArrayList<>();
-        for (TransportInfo transport : transports) {
-            ResolveInfo info = new ResolveInfo();
-            info.serviceInfo = new ServiceInfo();
-            info.serviceInfo.packageName = packageName;
-            info.serviceInfo.name = transport.name;
-            transportsInfo.add(info);
-        }
-
-        Intent intent = new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST);
-        intent.setPackage(packageName);
-
-        mPackageManagerShadow.addResolveInfoForIntent(intent, transportsInfo);
+        mShadowPackageManager.addPackage(packageInfo);
     }
 
-    private TransportManager createTransportManagerAndSetUpTransports(
-            List<TransportInfo> availableTransports, String defaultTransportName) throws Exception {
-        return createTransportManagerAndSetUpTransports(availableTransports,
-                Collections.<TransportInfo>emptyList(), defaultTransportName);
-    }
-
-    private TransportManager createTransportManagerAndSetUpTransports(
-            List<TransportInfo> availableTransports, List<TransportInfo> unavailableTransports,
-            String defaultTransportName)
-            throws Exception {
-        List<String> availableTransportsNames = new ArrayList<>();
-        List<ComponentName> availableTransportsComponentNames = new ArrayList<>();
-        for (TransportInfo transport : availableTransports) {
-            availableTransportsNames.add(transport.name);
-            availableTransportsComponentNames.add(transport.componentName);
-        }
-
-        List<ComponentName> allTransportsComponentNames = new ArrayList<>();
-        allTransportsComponentNames.addAll(availableTransportsComponentNames);
-        for (TransportInfo transport : unavailableTransports) {
-            allTransportsComponentNames.add(transport.componentName);
-        }
-
-        for (TransportInfo transport : unavailableTransports) {
-            ShadowContextImplForBackup.sUnbindableComponents.add(transport.componentName);
-        }
-
-        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                new HashSet<>(allTransportsComponentNames),
-                defaultTransportName,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-        transportManager.onPackageAdded(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                availableTransportsComponentNames);
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                availableTransportsNames);
-        for (TransportInfo transport : availableTransports) {
-            assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface))
-                    .isTrue();
-        }
-        for (TransportInfo transport : unavailableTransports) {
-            assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface))
-                    .isFalse();
-        }
-
-        mTransportBoundListenerStub.resetState();
-
+    private TransportManager createTransportManager(
+            @Nullable TransportData selectedTransport, TransportData... transports) {
+        Set<ComponentName> whitelist =
+                concat(Stream.of(selectedTransport), Stream.of(transports))
+                        .filter(Objects::nonNull)
+                        .map(TransportData::getTransportComponent)
+                        .collect(toSet());
+        TransportManager transportManager =
+                new TransportManager(
+                        mContext,
+                        whitelist,
+                        selectedTransport != null ? selectedTransport.transportName : null,
+                        mTransportClientManager);
+        transportManager.setOnTransportRegisteredListener(mListener);
         return transportManager;
     }
 
-    private static class TransportInfo {
-        public final String packageName;
-        public final String name;
-        public final ComponentName componentName;
-        public final IBackupTransport binderInterface;
-        public final IBinder binder;
-
-        TransportInfo(
-                String packageName,
-                String name,
-                @Nullable Intent configurationIntent,
-                String currentDestinationString,
-                @Nullable Intent dataManagementIntent,
-                String dataManagementLabel) {
-            this.packageName = packageName;
-            this.name = name;
-            this.componentName = new ComponentName(packageName, name);
-            this.binder = mock(IBinder.class);
-            IBackupTransport transport = mock(IBackupTransport.class);
-            try {
-                when(transport.name()).thenReturn(name);
-                when(transport.configurationIntent()).thenReturn(configurationIntent);
-                when(transport.currentDestinationString()).thenReturn(currentDestinationString);
-                when(transport.dataManagementIntent()).thenReturn(dataManagementIntent);
-                when(transport.dataManagementLabel()).thenReturn(dataManagementLabel);
-            } catch (RemoteException e) {
-                // Only here to mock methods that throw RemoteException
-            }
-            this.binderInterface = transport;
-        }
+    private void assertRegisteredTransports(
+            TransportManager transportManager, List<TransportData> transports) {
+        assertThat(transportManager.getRegisteredTransportComponents())
+                .asList()
+                .containsExactlyElementsIn(
+                        transports
+                                .stream()
+                                .map(TransportData::getTransportComponent)
+                                .collect(toList()));
+        assertThat(transportManager.getRegisteredTransportNames())
+                .asList()
+                .containsExactlyElementsIn(
+                        transports.stream().map(t -> t.transportName).collect(toList()));
     }
-
 }
diff --git a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
index dfca901..ace0441 100644
--- a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
+++ b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
@@ -19,8 +19,10 @@
 import static android.app.backup.BackupTransport.TRANSPORT_ERROR;
 import static android.app.backup.BackupTransport.TRANSPORT_OK;
 
-import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAME;
-import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES;
+import static com.android.server.backup.testing.TransportData.backupTransport;
+import static com.android.server.backup.testing.TransportData.d2dTransport;
+import static com.android.server.backup.testing.TransportData.localTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpTransports;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -28,7 +30,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -44,7 +45,8 @@
 import com.android.server.backup.RefactoredBackupManagerService;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.testing.TransportTestUtils;
-import com.android.server.backup.testing.TransportTestUtils.TransportData;
+import com.android.server.backup.testing.TransportData;
+import com.android.server.backup.testing.TransportTestUtils.TransportMock;
 import com.android.server.backup.transport.TransportClient;
 import com.android.server.testing.FrameworkRobolectricTestRunner;
 import com.android.server.testing.SystemLoaderClasses;
@@ -58,7 +60,10 @@
 import org.robolectric.annotation.Config;
 
 import java.io.File;
+import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
+import java.util.stream.Stream;
 
 @RunWith(FrameworkRobolectricTestRunner.class)
 @Config(manifest = Config.NONE, sdk = 26)
@@ -68,16 +73,21 @@
     @Mock private RefactoredBackupManagerService mBackupManagerService;
     @Mock private TransportManager mTransportManager;
     @Mock private OnTaskFinishedListener mListener;
-    @Mock private IBackupTransport mTransport;
+    @Mock private IBackupTransport mTransportBinder;
     @Mock private IBackupObserver mObserver;
     @Mock private AlarmManager mAlarmManager;
     @Mock private PendingIntent mRunInitIntent;
     private File mBaseStateDir;
+    private TransportData mTransport;
+    private String mTransportName;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        mTransport = backupTransport();
+        mTransportName = mTransport.transportName;
+
         Application context = RuntimeEnvironment.application;
         mBaseStateDir = new File(context.getCacheDir(), "base_state_dir");
         assertThat(mBaseStateDir.mkdir()).isTrue();
@@ -88,82 +98,76 @@
 
     @Test
     public void testRun_callsTransportCorrectly() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mTransport).initializeDevice();
-        verify(mTransport).finishBackup();
+        verify(mTransportBinder).initializeDevice();
+        verify(mTransportBinder).finishBackup();
     }
 
     @Test
     public void testRun_callsBackupManagerCorrectly() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
         verify(mBackupManagerService)
-                .recordInitPending(
-                        false, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME));
+                .recordInitPending(false, mTransportName, mTransport.transportDirName);
         verify(mBackupManagerService)
-                .resetBackupState(
-                        eq(
-                                new File(
-                                        mBaseStateDir,
-                                        TransportTestUtils.transportDirName(TRANSPORT_NAME))));
+                .resetBackupState(eq(new File(mBaseStateDir, mTransport.transportDirName)));
     }
 
     @Test
     public void testRun_callsObserverAndListenerCorrectly() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_OK));
+        verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_OK));
         verify(mObserver).backupFinished(eq(TRANSPORT_OK));
         verify(mListener).onFinished(any());
     }
 
     @Test
     public void testRun_whenInitializeDeviceFails() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_ERROR, 0);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mTransport).initializeDevice();
-        verify(mTransport, never()).finishBackup();
+        verify(mTransportBinder).initializeDevice();
+        verify(mTransportBinder, never()).finishBackup();
         verify(mBackupManagerService)
-                .recordInitPending(
-                        true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME));
+                .recordInitPending(true, mTransportName, mTransport.transportDirName);
     }
 
     @Test
     public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly()
             throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_ERROR, 0);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR));
+        verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR));
         verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
         verify(mListener).onFinished(any());
     }
 
     @Test
     public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_ERROR, 0);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
@@ -172,37 +176,36 @@
 
     @Test
     public void testRun_whenFinishBackupFails() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mTransport).initializeDevice();
-        verify(mTransport).finishBackup();
+        verify(mTransportBinder).initializeDevice();
+        verify(mTransportBinder).finishBackup();
         verify(mBackupManagerService)
-                .recordInitPending(
-                        true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME));
+                .recordInitPending(true, mTransportName, mTransport.transportDirName);
     }
 
     @Test
     public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR));
+        verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR));
         verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
         verify(mListener).onFinished(any());
     }
 
     @Test
     public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
@@ -211,64 +214,76 @@
 
     @Test
     public void testRun_whenOnlyOneTransportFails() throws Exception {
-        List<TransportData> transports =
-                TransportTestUtils.setUpTransports(
-                        mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
-        configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
-        configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+        TransportData transport1 = backupTransport();
+        TransportData transport2 = d2dTransport();
+        List<TransportMock> transportMocks =
+                setUpTransports(mTransportManager, transport1, transport2);
+        configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0);
+        configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK);
         PerformInitializeTask performInitializeTask =
-                createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+                createPerformInitializeTask(transport1.transportName, transport2.transportName);
 
         performInitializeTask.run();
 
-        verify(transports.get(1).transportMock).initializeDevice();
-        verify(mObserver).onResult(eq(TRANSPORT_NAMES[0]), eq(TRANSPORT_ERROR));
-        verify(mObserver).onResult(eq(TRANSPORT_NAMES[1]), eq(TRANSPORT_OK));
+        verify(transportMocks.get(1).transport).initializeDevice();
+        verify(mObserver).onResult(eq(transport1.transportName), eq(TRANSPORT_ERROR));
+        verify(mObserver).onResult(eq(transport2.transportName), eq(TRANSPORT_OK));
         verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
     }
 
     @Test
     public void testRun_withMultipleTransports() throws Exception {
-        List<TransportData> transports =
-                TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES);
-        configureTransport(transports.get(0).transportMock, TRANSPORT_OK, TRANSPORT_OK);
-        configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
-        configureTransport(transports.get(2).transportMock, TRANSPORT_OK, TRANSPORT_OK);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAMES);
+        List<TransportMock> transportMocks =
+                setUpTransports(
+                        mTransportManager, backupTransport(), d2dTransport(), localTransport());
+        configureTransport(transportMocks.get(0).transport, TRANSPORT_OK, TRANSPORT_OK);
+        configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK);
+        configureTransport(transportMocks.get(2).transport, TRANSPORT_OK, TRANSPORT_OK);
+        String[] transportNames =
+                Stream.of(new TransportData[] {backupTransport(), d2dTransport(), localTransport()})
+                        .map(t -> t.transportName)
+                        .toArray(String[]::new);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(transportNames);
 
         performInitializeTask.run();
 
-        for (TransportData transport : transports) {
+        Iterator<TransportData> transportsIterator =
+                Arrays.asList(
+                                new TransportData[] {
+                                    backupTransport(), d2dTransport(), localTransport()
+                                })
+                        .iterator();
+        for (TransportMock transportMock : transportMocks) {
+            TransportData transport = transportsIterator.next();
             verify(mTransportManager).getTransportClient(eq(transport.transportName), any());
             verify(mTransportManager)
-                    .disposeOfTransportClient(eq(transport.transportClientMock), any());
+                    .disposeOfTransportClient(eq(transportMock.transportClient), any());
         }
     }
 
     @Test
     public void testRun_whenOnlyOneTransportFails_disposesAllTransports() throws Exception {
-        List<TransportData> transports =
-                TransportTestUtils.setUpTransports(
-                        mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
-        configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
-        configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+        TransportData transport1 = backupTransport();
+        TransportData transport2 = d2dTransport();
+        List<TransportMock> transportMocks =
+                setUpTransports(mTransportManager, transport1, transport2);
+        configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0);
+        configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK);
         PerformInitializeTask performInitializeTask =
-                createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+                createPerformInitializeTask(transport1.transportName, transport2.transportName);
 
         performInitializeTask.run();
 
         verify(mTransportManager)
-                .disposeOfTransportClient(eq(transports.get(0).transportClientMock), any());
+                .disposeOfTransportClient(eq(transportMocks.get(0).transportClient), any());
         verify(mTransportManager)
-                .disposeOfTransportClient(eq(transports.get(1).transportClientMock), any());
+                .disposeOfTransportClient(eq(transportMocks.get(1).transportClient), any());
     }
 
     @Test
     public void testRun_whenTransportNotRegistered() throws Exception {
-        TransportTestUtils.setUpTransports(
-                mTransportManager, new TransportData(TRANSPORT_NAME, null, null));
-
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransports(mTransportManager, mTransport.unregistered());
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
@@ -279,16 +294,15 @@
 
     @Test
     public void testRun_whenOnlyOneTransportNotRegistered() throws Exception {
-        List<TransportData> transports =
-                TransportTestUtils.setUpTransports(
-                        mTransportManager,
-                        new TransportData(TRANSPORT_NAMES[0], null, null),
-                        new TransportData(TRANSPORT_NAMES[1]));
-        String registeredTransportName = transports.get(1).transportName;
-        IBackupTransport registeredTransport = transports.get(1).transportMock;
-        TransportClient registeredTransportClient = transports.get(1).transportClientMock;
+        TransportData transport1 = backupTransport().unregistered();
+        TransportData transport2 = d2dTransport();
+        List<TransportMock> transportMocks =
+                setUpTransports(mTransportManager, transport1, transport2);
+        String registeredTransportName = transport2.transportName;
+        IBackupTransport registeredTransport = transportMocks.get(1).transport;
+        TransportClient registeredTransportClient = transportMocks.get(1).transportClient;
         PerformInitializeTask performInitializeTask =
-                createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+                createPerformInitializeTask(transport1.transportName, transport2.transportName);
 
         performInitializeTask.run();
 
@@ -299,25 +313,24 @@
 
     @Test
     public void testRun_whenTransportNotAvailable() throws Exception {
-        TransportClient transportClient = mock(TransportClient.class);
-        TransportTestUtils.setUpTransports(
-                mTransportManager, new TransportData(TRANSPORT_NAME, null, transportClient));
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        TransportMock transportMock = setUpTransport(mTransport.unavailable());
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any());
+        verify(mTransportManager)
+                .disposeOfTransportClient(eq(transportMock.transportClient), any());
         verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
         verify(mListener).onFinished(any());
     }
 
     @Test
     public void testRun_whenTransportThrowsDeadObjectException() throws Exception {
-        TransportClient transportClient = mock(TransportClient.class);
-        TransportTestUtils.setUpTransports(
-                mTransportManager, new TransportData(TRANSPORT_NAME, mTransport, transportClient));
-        when(mTransport.initializeDevice()).thenThrow(DeadObjectException.class);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        TransportMock transportMock = setUpTransport(mTransport);
+        IBackupTransport transport = transportMock.transport;
+        TransportClient transportClient = transportMock.transportClient;
+        when(transport.initializeDevice()).thenThrow(DeadObjectException.class);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
@@ -343,9 +356,10 @@
         when(transportMock.finishBackup()).thenReturn(finishBackupStatus);
     }
 
-    private void setUpTransport(String transportName) throws Exception {
-        TransportTestUtils.setUpTransport(
-                mTransportManager,
-                new TransportData(transportName, mTransport, mock(TransportClient.class)));
+    private TransportMock setUpTransport(TransportData transport) throws Exception {
+        TransportMock transportMock =
+                TransportTestUtils.setUpTransport(mTransportManager, transport);
+        mTransportBinder = transportMock.transport;
+        return transportMock;
     }
 }
diff --git a/services/robotests/src/com/android/server/backup/testing/TestUtils.java b/services/robotests/src/com/android/server/backup/testing/TestUtils.java
new file mode 100644
index 0000000..1be298d
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/testing/TestUtils.java
@@ -0,0 +1,68 @@
+/*
+ * 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
+ */
+
+package com.android.server.backup.testing;
+
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+
+import java.util.concurrent.Callable;
+
+public class TestUtils {
+    /**
+     * Calls {@link Runnable#run()} and returns if no exception is thrown. Otherwise, if the
+     * exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} and
+     * throw.
+     *
+     * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure
+     * in a test.
+     */
+    public static void uncheck(ThrowingRunnable runnable) {
+        try {
+            runnable.runOrThrow();
+        } catch (Exception e) {
+            throw wrapIfChecked(e);
+        }
+    }
+
+    /**
+     * Calls {@link Callable#call()} and returns the value if no exception is thrown. Otherwise, if
+     * the exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException}
+     * and throw.
+     *
+     * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure
+     * in a test.
+     */
+    public static <T> T uncheck(Callable<T> callable) {
+        try {
+            return callable.call();
+        } catch (Exception e) {
+            throw wrapIfChecked(e);
+        }
+    }
+
+    /**
+     * Wrap {@code e} in a {@link RuntimeException} only if it's not one already, in which case it's
+     * returned.
+     */
+    public static RuntimeException wrapIfChecked(Exception e) {
+        if (e instanceof RuntimeException) {
+            return (RuntimeException) e;
+        }
+        return new RuntimeException(e);
+    }
+
+    private TestUtils() {}
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java b/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java
deleted file mode 100644
index 84ac2c2..0000000
--- a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java
+++ /dev/null
@@ -1,64 +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.backup.testing;
-
-import com.android.internal.backup.IBackupTransport;
-import com.android.server.backup.TransportManager;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Stub implementation of TransportBoundListener, which returns given result and can tell whether
- * it was called for given transport.
- */
-public class TransportBoundListenerStub implements
-        TransportManager.TransportBoundListener {
-    private boolean mAlwaysReturnSuccess;
-    private Set<IBackupTransport> mTransportsCalledFor = new HashSet<>();
-
-    public TransportBoundListenerStub(boolean alwaysReturnSuccess) {
-        this.mAlwaysReturnSuccess = alwaysReturnSuccess;
-    }
-
-    @Override
-    public boolean onTransportBound(IBackupTransport binder) {
-        mTransportsCalledFor.add(binder);
-        return mAlwaysReturnSuccess;
-    }
-
-    /**
-     * Returns whether the listener was called for the specified transport at least once.
-     */
-    public boolean isCalledForTransport(IBackupTransport binder) {
-        return mTransportsCalledFor.contains(binder);
-    }
-
-    /**
-     * Returns whether the listener was called at least once.
-     */
-    public boolean isCalled() {
-        return !mTransportsCalledFor.isEmpty();
-    }
-
-    /**
-     * Resets listener calls.
-     */
-    public void resetState() {
-        mTransportsCalledFor.clear();
-    }
-}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportData.java b/services/robotests/src/com/android/server/backup/testing/TransportData.java
new file mode 100644
index 0000000..9feaa8e
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/testing/TransportData.java
@@ -0,0 +1,149 @@
+/*
+ * 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
+ */
+
+package com.android.server.backup.testing;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Intent;
+
+public class TransportData {
+    // No constants since new Intent() can't be called in static context because of Robolectric
+    public static TransportData backupTransport() {
+        return new TransportData(
+                "com.google.android.gms/.backup.BackupTransportService",
+                "com.google.android.gms/.backup.BackupTransportService",
+                "com.google.android.gms.backup.BackupTransportService",
+                new Intent(),
+                "user@gmail.com",
+                new Intent(),
+                "Google Account");
+    }
+
+    public static TransportData d2dTransport() {
+        return new TransportData(
+                "com.google.android.gms/.backup.migrate.service.D2dTransport",
+                "com.google.android.gms/.backup.component.D2dTransportService",
+                "d2dMigrateTransport",
+                null,
+                "Moving data to new device",
+                null,
+                "");
+    }
+
+    public static TransportData localTransport() {
+        return new TransportData(
+                "android/com.android.internal.backup.LocalTransport",
+                "android/com.android.internal.backup.LocalTransportService",
+                "com.android.internal.backup.LocalTransport",
+                null,
+                "Backing up to debug-only private cache",
+                null,
+                "");
+    }
+
+    public static TransportData genericTransport(String packageName, String className) {
+        return new TransportData(
+                packageName + "/." + className,
+                packageName + "/." + className + "Service",
+                packageName + "." + className,
+                new Intent(),
+                "currentDestinationString",
+                new Intent(),
+                "dataManagementLabel");
+    }
+
+    @TransportTestUtils.TransportStatus
+    public int transportStatus;
+    public final String transportName;
+    private final String transportComponentShort;
+    @Nullable
+    public String transportDirName;
+    @Nullable public Intent configurationIntent;
+    @Nullable public String currentDestinationString;
+    @Nullable public Intent dataManagementIntent;
+    @Nullable public String dataManagementLabel;
+
+    private TransportData(
+            @TransportTestUtils.TransportStatus int transportStatus,
+            String transportName,
+            String transportComponentShort,
+            String transportDirName,
+            Intent configurationIntent,
+            String currentDestinationString,
+            Intent dataManagementIntent,
+            String dataManagementLabel) {
+        this.transportStatus = transportStatus;
+        this.transportName = transportName;
+        this.transportComponentShort = transportComponentShort;
+        this.transportDirName = transportDirName;
+        this.configurationIntent = configurationIntent;
+        this.currentDestinationString = currentDestinationString;
+        this.dataManagementIntent = dataManagementIntent;
+        this.dataManagementLabel = dataManagementLabel;
+    }
+
+    public TransportData(
+            String transportName,
+            String transportComponentShort,
+            String transportDirName,
+            Intent configurationIntent,
+            String currentDestinationString,
+            Intent dataManagementIntent,
+            String dataManagementLabel) {
+        this(
+                TransportTestUtils.TransportStatus.REGISTERED_AVAILABLE,
+                transportName,
+                transportComponentShort,
+                transportDirName,
+                configurationIntent,
+                currentDestinationString,
+                dataManagementIntent,
+                dataManagementLabel);
+    }
+
+    /**
+     * Not field because otherwise we'd have to call ComponentName::new in static context and
+     * Robolectric does not like this.
+     */
+    public ComponentName getTransportComponent() {
+        return ComponentName.unflattenFromString(transportComponentShort);
+    }
+
+    public TransportData unavailable() {
+        return new TransportData(
+                TransportTestUtils.TransportStatus.REGISTERED_UNAVAILABLE,
+                transportName,
+                transportComponentShort,
+                transportDirName,
+                configurationIntent,
+                currentDestinationString,
+                dataManagementIntent,
+                dataManagementLabel);
+    }
+
+    public TransportData unregistered() {
+        return new TransportData(
+                TransportTestUtils.TransportStatus.UNREGISTERED,
+                transportName,
+                transportComponentShort,
+                transportDirName,
+                configurationIntent,
+                currentDestinationString,
+                dataManagementIntent,
+                dataManagementLabel);
+    }
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
index 9770e40..e1dc7b5 100644
--- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
+++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
@@ -16,13 +16,23 @@
 
 package com.android.server.backup.testing;
 
+import static com.android.server.backup.testing.TestUtils.uncheck;
+
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import static java.util.stream.Collectors.toList;
+
 import android.annotation.Nullable;
 import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.support.annotation.IntDef;
 
 import com.android.internal.backup.IBackupTransport;
 import com.android.server.backup.TransportManager;
@@ -30,85 +40,82 @@
 import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.transport.TransportNotRegisteredException;
 
-import java.util.Arrays;
+import org.robolectric.shadows.ShadowPackageManager;
+
 import java.util.List;
+import java.util.stream.Stream;
 
 public class TransportTestUtils {
-    public static final String[] TRANSPORT_NAMES = {
-        "android/com.android.internal.backup.LocalTransport",
-        "com.google.android.gms/.backup.migrate.service.D2dTransport",
-        "com.google.android.gms/.backup.BackupTransportService"
-    };
+    /**
+     * Differently from {@link #setUpTransports(TransportManager, TransportData...)}, which
+     * configures {@link TransportManager}, this is meant to mock the environment for a real
+     * TransportManager.
+     */
+    public static void setUpTransportsForTransportManager(
+            ShadowPackageManager shadowPackageManager, TransportData... transports)
+            throws Exception {
+        for (TransportData transport : transports) {
+            ComponentName transportComponent = transport.getTransportComponent();
+            String packageName = transportComponent.getPackageName();
+            ResolveInfo resolveInfo = resolveInfo(transportComponent);
+            shadowPackageManager.addResolveInfoForIntent(transportIntent(), resolveInfo);
+            shadowPackageManager.addResolveInfoForIntent(
+                    transportIntent().setPackage(packageName), resolveInfo);
+        }
+    }
 
-    public static final String TRANSPORT_NAME = TRANSPORT_NAMES[0];
+    private static Intent transportIntent() {
+        return new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST);
+    }
 
-    /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */
-    public static TransportData setUpCurrentTransport(
-            TransportManager transportManager, String transportName) throws Exception {
-        TransportData transport = setUpTransports(transportManager, transportName).get(0);
-        when(transportManager.getCurrentTransportClient(any()))
-                .thenReturn(transport.transportClientMock);
-        return transport;
+    private static ResolveInfo resolveInfo(ComponentName transportComponent) {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = transportComponent.getPackageName();
+        resolveInfo.serviceInfo.name = transportComponent.getClassName();
+        return resolveInfo;
     }
 
     /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */
-    public static List<TransportData> setUpTransports(
-            TransportManager transportManager, String... transportNames) throws Exception {
-        return setUpTransports(
-                transportManager,
-                Arrays.stream(transportNames)
-                        .map(TransportData::new)
-                        .toArray(TransportData[]::new));
+    public static TransportMock setUpCurrentTransport(
+            TransportManager transportManager, TransportData transport) throws Exception {
+        TransportMock transportMock = setUpTransports(transportManager, transport).get(0);
+        if (transportMock.transportClient != null) {
+            when(transportManager.getCurrentTransportClient(any()))
+                    .thenReturn(transportMock.transportClient);
+        }
+        return transportMock;
     }
 
     /** @see #setUpTransport(TransportManager, TransportData) */
-    public static List<TransportData> setUpTransports(
+    public static List<TransportMock> setUpTransports(
             TransportManager transportManager, TransportData... transports) throws Exception {
-        for (TransportData transport : transports) {
-            setUpTransport(transportManager, transport);
-        }
-        return Arrays.asList(transports);
+        return Stream.of(transports)
+                .map(transport -> uncheck(() -> setUpTransport(transportManager, transport)))
+                .collect(toList());
     }
 
-    /**
-     * Configures transport according to {@link TransportData}:
-     *
-     * <ul>
-     *   <li>{@link TransportData#transportMock} {@code null} means transport not available.
-     *   <li>{@link TransportData#transportClientMock} {@code null} means transport not registered.
-     * </ul>
-     */
-    public static void setUpTransport(TransportManager transportManager, TransportData transport)
-            throws Exception {
+    public static TransportMock setUpTransport(
+            TransportManager transportManager, TransportData transport) throws Exception {
+        int status = transport.transportStatus;
         String transportName = transport.transportName;
-        String transportDirName = transportDirName(transportName);
-        ComponentName transportComponent = transportComponentName(transportName);
-        IBackupTransport transportMock = transport.transportMock;
-        TransportClient transportClientMock = transport.transportClientMock;
+        ComponentName transportComponent = transport.getTransportComponent();
+        String transportDirName = transport.transportDirName;
 
-        if (transportClientMock != null) {
+        TransportMock transportMock = mockTransport(transport);
+        if (status == TransportStatus.REGISTERED_AVAILABLE
+                || status == TransportStatus.REGISTERED_UNAVAILABLE) {
             // Transport registered
             when(transportManager.getTransportClient(eq(transportName), any()))
-                    .thenReturn(transportClientMock);
+                    .thenReturn(transportMock.transportClient);
             when(transportManager.getTransportClientOrThrow(eq(transportName), any()))
-                    .thenReturn(transportClientMock);
+                    .thenReturn(transportMock.transportClient);
             when(transportManager.getTransportName(transportComponent)).thenReturn(transportName);
             when(transportManager.getTransportDirName(eq(transportName)))
                     .thenReturn(transportDirName);
             when(transportManager.getTransportDirName(eq(transportComponent)))
                     .thenReturn(transportDirName);
-            when(transportClientMock.getTransportComponent()).thenReturn(transportComponent);
-
-            if (transportMock != null) {
-                // Transport registered and available
-                when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock);
-                when(transportMock.name()).thenReturn(transportName);
-                when(transportMock.transportDirName()).thenReturn(transportDirName);
-            } else {
-                // Transport registered but unavailable
-                when(transportClientMock.connectOrThrow(any()))
-                        .thenThrow(TransportNotAvailableException.class);
-            }
+            // TODO: Mock rest of description methods
         } else {
             // Transport not registered
             when(transportManager.getTransportClient(eq(transportName), any())).thenReturn(null);
@@ -121,34 +128,73 @@
             when(transportManager.getTransportDirName(eq(transportComponent)))
                     .thenThrow(TransportNotRegisteredException.class);
         }
+        return transportMock;
     }
 
-    /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */
-    public static ComponentName transportComponentName(String transportName) {
-        return ComponentName.unflattenFromString(transportName);
-    }
+    public static TransportMock mockTransport(TransportData transport) throws Exception {
+        final TransportClient transportClientMock;
+        int status = transport.transportStatus;
+        ComponentName transportComponent = transport.getTransportComponent();
+        if (status == TransportStatus.REGISTERED_AVAILABLE
+                || status == TransportStatus.REGISTERED_UNAVAILABLE) {
+            // Transport registered
+            transportClientMock = mock(TransportClient.class);
+            when(transportClientMock.getTransportComponent()).thenReturn(transportComponent);
+            if (status == TransportStatus.REGISTERED_AVAILABLE) {
+                // Transport registered and available
+                IBackupTransport transportMock = mockTransportBinder(transport);
+                when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock);
 
-    public static String transportDirName(String transportName) {
-        return transportName + "_dir_name";
-    }
+                return new TransportMock(transportClientMock, transportMock);
+            } else {
+                // Transport registered but unavailable
+                when(transportClientMock.connectOrThrow(any()))
+                        .thenThrow(TransportNotAvailableException.class);
 
-    public static class TransportData {
-        public final String transportName;
-        @Nullable public final IBackupTransport transportMock;
-        @Nullable public final TransportClient transportClientMock;
-
-        public TransportData(
-                String transportName,
-                @Nullable IBackupTransport transportMock,
-                @Nullable TransportClient transportClientMock) {
-            this.transportName = transportName;
-            this.transportMock = transportMock;
-            this.transportClientMock = transportClientMock;
+                return new TransportMock(transportClientMock, null);
+            }
+        } else {
+            // Transport not registered
+            return new TransportMock(null, null);
         }
+    }
 
-        public TransportData(String transportName) {
-            this(transportName, mock(IBackupTransport.class), mock(TransportClient.class));
+    private static IBackupTransport mockTransportBinder(TransportData transport) throws Exception {
+        IBackupTransport transportBinder = mock(IBackupTransport.class);
+        try {
+            when(transportBinder.name()).thenReturn(transport.transportName);
+            when(transportBinder.transportDirName()).thenReturn(transport.transportDirName);
+            when(transportBinder.configurationIntent()).thenReturn(transport.configurationIntent);
+            when(transportBinder.currentDestinationString())
+                    .thenReturn(transport.currentDestinationString);
+            when(transportBinder.dataManagementIntent()).thenReturn(transport.dataManagementIntent);
+            when(transportBinder.dataManagementLabel()).thenReturn(transport.dataManagementLabel);
+        } catch (RemoteException e) {
+            fail("RemoteException?");
         }
+        return transportBinder;
+    }
+
+    public static class TransportMock {
+        @Nullable public final TransportClient transportClient;
+        @Nullable public final IBackupTransport transport;
+
+        private TransportMock(
+                @Nullable TransportClient transportClient, @Nullable IBackupTransport transport) {
+            this.transportClient = transportClient;
+            this.transport = transport;
+        }
+    }
+
+    @IntDef({
+        TransportStatus.REGISTERED_AVAILABLE,
+        TransportStatus.REGISTERED_UNAVAILABLE,
+        TransportStatus.UNREGISTERED
+    })
+    public @interface TransportStatus {
+        int REGISTERED_AVAILABLE = 0;
+        int REGISTERED_UNAVAILABLE = 1;
+        int UNREGISTERED = 2;
     }
 
     private TransportTestUtils() {}
diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
index 3bc0b30..9b4dec6 100644
--- a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
+++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
@@ -17,9 +17,7 @@
 package com.android.server.backup.transport;
 
 import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -36,12 +34,10 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
-
 import com.android.internal.backup.IBackupTransport;
 import com.android.server.backup.TransportManager;
 import com.android.server.testing.FrameworkRobolectricTestRunner;
 import com.android.server.testing.SystemLoaderClasses;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -78,11 +74,7 @@
         mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent);
         mTransportClient =
                 new TransportClient(
-                        mContext,
-                        mBindIntent,
-                        mTransportComponent,
-                        "1",
-                        new Handler(mainLooper));
+                        mContext, mBindIntent, mTransportComponent, "1", new Handler(mainLooper));
 
         when(mContext.bindServiceAsUser(
                         eq(mBindIntent),
@@ -213,32 +205,34 @@
                 .onTransportConnectionResult(isNull(), eq(mTransportClient));
     }
 
-    // TODO(b/69153972): Support SDK 26 API (ServiceConnection.inBindingDied) for transport tests
-    /*@Test
+    @Test
     public void testConnectAsync_callsListenerIfBindingDies() throws Exception {
-        mTransportClient.connectAsync(mTransportListener, "caller");
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller");
 
         ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
         connection.onBindingDied(mTransportComponent);
 
         mShadowLooper.runToEndOfTasks();
-        verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
+        verify(mTransportConnectionListener)
+                .onTransportConnectionResult(isNull(), eq(mTransportClient));
     }
 
     @Test
     public void testConnectAsync_whenPendingConnection_callsListenersIfBindingDies()
             throws Exception {
-        mTransportClient.connectAsync(mTransportListener, "caller1");
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
         ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
 
-        mTransportClient.connectAsync(mTransportListener2, "caller2");
+        mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
 
         connection.onBindingDied(mTransportComponent);
 
         mShadowLooper.runToEndOfTasks();
-        verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
-        verify(mTransportListener2).onTransportBound(isNull(), eq(mTransportClient));
-    }*/
+        verify(mTransportConnectionListener)
+                .onTransportConnectionResult(isNull(), eq(mTransportClient));
+        verify(mTransportConnectionListener2)
+                .onTransportConnectionResult(isNull(), eq(mTransportClient));
+    }
 
     private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) {
         ArgumentCaptor<ServiceConnection> connectionCaptor =
diff --git a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java
new file mode 100644
index 0000000..6d22073
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java
@@ -0,0 +1,37 @@
+/*
+ * 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
+ */
+
+package com.android.server.testing.shadows;
+
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.UserHandle;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowContextImpl;
+
+@Implements(className = ShadowContextImpl.CLASS_NAME, inheritImplementationMethods = true)
+public class FrameworkShadowContextImpl extends ShadowContextImpl {
+    @Implementation
+    public boolean bindServiceAsUser(
+            Intent service,
+            ServiceConnection connection,
+            int flags,
+            UserHandle user) {
+        return bindService(service, connection, flags);
+    }
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java
similarity index 83%
rename from services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java
rename to services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java
index b64b59d..5cdbe7f 100644
--- a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java
+++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java
@@ -14,22 +14,18 @@
  * limitations under the License
  */
 
-package com.android.server.backup.testing;
+package com.android.server.testing.shadows;
 
 import android.app.ApplicationPackageManager;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
-
+import java.util.List;
 import org.robolectric.annotation.Implements;
 import org.robolectric.shadows.ShadowApplicationPackageManager;
 
-import java.util.List;
-
-/**
- * Implementation of PackageManager for Robolectric which handles queryIntentServicesAsUser().
- */
+/** Extension of ShadowApplicationPackageManager */
 @Implements(value = ApplicationPackageManager.class, inheritImplementationMethods = true)
-public class ShadowPackageManagerForBackup extends ShadowApplicationPackageManager {
+public class FrameworkShadowPackageManager extends ShadowApplicationPackageManager {
     @Override
     public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
         return queryIntentServices(intent, flags);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
index 1dba39f..b38a413 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -35,24 +35,23 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.PauseActivityItem;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.MutableBoolean;
 
 import org.junit.runner.RunWith;
 import org.junit.Before;
 import org.junit.Test;
-
-import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
 
 /**
  * Tests for the {@link ActivityRecord} class.
@@ -110,23 +109,20 @@
 
     @Test
     public void testPausingWhenVisibleFromStopped() throws Exception {
-        mActivity.state = STOPPED;
-        mActivity.makeVisibleIfNeeded(null /* starting */);
-        assertEquals(mActivity.state, PAUSING);
-
-        final ArgumentCaptor<ClientTransaction> transaction =
-                ArgumentCaptor.forClass(ClientTransaction.class);
-        verify(mActivity.app.thread, atLeast(1)).scheduleTransaction(transaction.capture());
-
-        boolean pauseFound = false;
-
-        for (ClientTransaction targetTransaction : transaction.getAllValues()) {
-            if (targetTransaction.getLifecycleStateRequest() instanceof PauseActivityItem) {
-                pauseFound = true;
+        final MutableBoolean pauseFound = new MutableBoolean(false);
+        doAnswer((InvocationOnMock invocationOnMock) -> {
+            final ClientTransaction transaction = invocationOnMock.getArgument(0);
+            if (transaction.getLifecycleStateRequest() instanceof PauseActivityItem) {
+                pauseFound.value = true;
             }
-        }
+            return null;
+        }).when(mActivity.app.thread).scheduleTransaction(any());
+        mActivity.state = STOPPED;
 
-        assertTrue(pauseFound);
+        mActivity.makeVisibleIfNeeded(null /* starting */);
+
+        assertEquals(mActivity.state, PAUSING);
+        assertTrue(pauseFound.value);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/am/ClientLifecycleManagerTests.java b/services/tests/servicestests/src/com/android/server/am/ClientLifecycleManagerTests.java
new file mode 100644
index 0000000..ef6d5e8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ClientLifecycleManagerTests.java
@@ -0,0 +1,44 @@
+package com.android.server.am;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.IApplicationThread;
+import android.app.servertransaction.ClientTransaction;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class ClientLifecycleManagerTests {
+
+    @Test
+    public void testScheduleAndRecycleBinderClientTransaction() throws Exception {
+        ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.class),
+                new Binder()));
+
+        ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager();
+        clientLifecycleManager.scheduleTransaction(item);
+
+        verify(item, times(1)).recycle();
+    }
+
+    @Test
+    public void testScheduleNoRecycleNonBinderClientTransaction() throws Exception {
+        ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.Stub.class),
+                new Binder()));
+
+        ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager();
+        clientLifecycleManager.scheduleTransaction(item);
+
+        verify(item, times(0)).recycle();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index 766d30d..7b4441a 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -96,7 +96,7 @@
         createBackupManagerService();
 
         verify(mTransportManager)
-                .setTransportBoundListener(any(TransportManager.TransportBoundListener.class));
+                .setOnTransportRegisteredListener(any());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 0650acb..5134f52 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -15,11 +15,13 @@
  */
 package com.android.server.devicepolicy;
 
+import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
 import android.app.IActivityManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.backup.IBackupManager;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.IPackageManager;
@@ -152,6 +154,11 @@
         }
 
         @Override
+        UsageStatsManagerInternal getUsageStatsManagerInternal() {
+            return services.usageStatsManagerInternal;
+        }
+
+        @Override
         PackageManagerInternal getPackageManagerInternal() {
             return services.packageManagerInternal;
         }
@@ -182,6 +189,11 @@
         }
 
         @Override
+        ActivityManagerInternal getActivityManagerInternal() {
+            return services.activityManagerInternal;
+        }
+
+        @Override
         IPackageManager getIPackageManager() {
             return services.ipackageManager;
         }
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 58ac7d2..1df0ff2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -44,6 +44,7 @@
 import static org.mockito.Mockito.timeout;
 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 static org.mockito.hamcrest.MockitoHamcrest.argThat;
@@ -86,10 +87,12 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener;
 import com.android.server.pm.UserRestrictionsUtils;
 
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
+import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
@@ -182,6 +185,7 @@
 
         initializeDpms();
 
+        Mockito.reset(getServices().usageStatsManagerInternal);
         setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
         setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
         setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID);
@@ -207,6 +211,7 @@
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
 
         dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
+        dpms.handleStart();
         dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
         dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
 
@@ -278,6 +283,32 @@
         assertNull(LocalServices.getService(DevicePolicyManagerInternal.class));
     }
 
+    public void testHandleStart() throws Exception {
+        // Device owner in SYSTEM_USER
+        setDeviceOwner();
+        // Profile owner in CALLER_USER_HANDLE
+        setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
+        setAsProfileOwner(admin2);
+        // Active admin in CALLER_USER_HANDLE
+        final int ANOTHER_UID = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, 1306);
+        setUpPackageManagerForFakeAdmin(adminAnotherPackage, ANOTHER_UID, admin2);
+        dpm.setActiveAdmin(adminAnotherPackage, /* replace =*/ false,
+                DpmMockContext.CALLER_USER_HANDLE);
+        assertTrue(dpm.isAdminActiveAsUser(adminAnotherPackage,
+                DpmMockContext.CALLER_USER_HANDLE));
+
+        initializeDpms();
+
+        // Verify
+        verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+                MockUtils.checkAdminApps(admin1.getPackageName()),
+                eq(UserHandle.USER_SYSTEM));
+        verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+                MockUtils.checkAdminApps(admin2.getPackageName(),
+                        adminAnotherPackage.getPackageName()),
+                eq(DpmMockContext.CALLER_USER_HANDLE));
+    }
+
     /**
      * Caller doesn't have proper permissions.
      */
@@ -330,6 +361,9 @@
                 eq(DpmMockContext.CALLER_USER_HANDLE),
                 anyString());
 
+        verify(getServices().usageStatsManagerInternal).onActiveAdminAdded(
+                admin1.getPackageName(), DpmMockContext.CALLER_USER_HANDLE);
+
         // TODO Verify other calls too.
 
         // Make sure it's active admin1.
@@ -369,6 +403,11 @@
                 eq(DpmMockContext.CALLER_USER_HANDLE),
                 anyString());
 
+        // times(2) because it was previously called for admin1 which is in the same package
+        // as admin2.
+        verify(getServices().usageStatsManagerInternal, times(2)).onActiveAdminAdded(
+                admin2.getPackageName(), DpmMockContext.CALLER_USER_HANDLE);
+
         // 4. Add the same admin1 again without replace, which should throw.
         assertExpectException(IllegalArgumentException.class, /* messageRegex= */ null,
                 () -> dpm.setActiveAdmin(admin1, /* replace =*/ false));
@@ -384,6 +423,10 @@
         assertEquals(admin1, admins.get(0));
         assertEquals(admin2, admins.get(1));
 
+        // There shouldn't be any callback to UsageStatsManagerInternal when the admin is being
+        // replaced
+        verifyNoMoreInteractions(getServices().usageStatsManagerInternal);
+
         // Another user has no admins.
         mContext.callerPermissions.add("android.permission.INTERACT_ACROSS_USERS_FULL");
 
@@ -516,6 +559,8 @@
                 () -> dpm.removeActiveAdmin(admin1));
 
         assertFalse(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
+        verify(getServices().usageStatsManagerInternal, times(0)).setActiveAdminApps(
+                null, DpmMockContext.CALLER_USER_HANDLE);
 
         // 2. User unlocked.
         when(getServices().userManager.isUserUnlocked(eq(DpmMockContext.CALLER_USER_HANDLE)))
@@ -523,6 +568,8 @@
 
         dpm.removeActiveAdmin(admin1);
         assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
+        verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+                null, DpmMockContext.CALLER_USER_HANDLE);
     }
 
     /**
@@ -547,6 +594,8 @@
 
         dpms.removeActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE);
         assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
+        verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+                null, DpmMockContext.CALLER_USER_HANDLE);
 
         // TODO DO Still can't be removed in this case.
     }
@@ -588,6 +637,8 @@
                 isNull(Bundle.class));
 
         assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
+        verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+                null, DpmMockContext.CALLER_USER_HANDLE);
 
         // Again broadcast from saveSettingsLocked().
         verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
@@ -598,6 +649,86 @@
         // TODO Check other internal calls.
     }
 
+    public void testRemoveActiveAdmin_multipleAdminsInUser() {
+        // Need MANAGE_DEVICE_ADMINS for setActiveAdmin.  We'll remove it later.
+        mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+
+        // Add admin1.
+        dpm.setActiveAdmin(admin1, /* replace =*/ false);
+
+        assertTrue(dpm.isAdminActive(admin1));
+        assertFalse(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
+
+        // Add admin2.
+        dpm.setActiveAdmin(admin2, /* replace =*/ false);
+
+        assertTrue(dpm.isAdminActive(admin2));
+        assertFalse(dpm.isRemovingAdmin(admin2, DpmMockContext.CALLER_USER_HANDLE));
+
+        // Broadcast from saveSettingsLocked().
+        verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+
+        // Remove.  No permissions, but same user, so it'll work.
+        mContext.callerPermissions.clear();
+        dpm.removeActiveAdmin(admin1);
+
+        verify(mContext.spiedContext).sendOrderedBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE),
+                isNull(String.class),
+                any(BroadcastReceiver.class),
+                eq(dpms.mHandler),
+                eq(Activity.RESULT_OK),
+                isNull(String.class),
+                isNull(Bundle.class));
+
+        assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
+        verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+                MockUtils.checkAdminApps(admin2.getPackageName()),
+                eq(DpmMockContext.CALLER_USER_HANDLE));
+
+        // Again broadcast from saveSettingsLocked().
+        verify(mContext.spiedContext, times(3)).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+    }
+
+    /**
+     * Test for:
+     * {@link DevicePolicyManager#forceRemoveActiveAdmin(ComponentName, int)}
+     */
+    public void testForceRemoveActiveAdmin() throws Exception {
+        mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+
+        // Add admin.
+        setupPackageInPackageManager(admin1.getPackageName(),
+                /* userId= */ DpmMockContext.CALLER_USER_HANDLE,
+                /* appId= */ 10138,
+                /* flags= */ ApplicationInfo.FLAG_TEST_ONLY);
+        dpm.setActiveAdmin(admin1, /* replace =*/ false);
+        assertTrue(dpm.isAdminActive(admin1));
+
+        // Calling from a non-shell uid should fail with a SecurityException
+        mContext.binder.callingUid = 123456;
+        assertExpectException(SecurityException.class,
+                /* messageRegex =*/ "Non-shell user attempted to call",
+                () -> dpms.forceRemoveActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
+
+        mContext.binder.callingUid = Process.SHELL_UID;
+        dpms.forceRemoveActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+        mContext.callerPermissions.add(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        // Verify
+        assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
+        verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+                null, DpmMockContext.CALLER_USER_HANDLE);
+    }
+
     /**
      * Test for: @{link DevicePolicyManager#setActivePasswordState}
      *
@@ -954,6 +1085,9 @@
                 eq(null),
                 eq(true), eq(CAMERA_NOT_DISABLED));
 
+        verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+                null, UserHandle.USER_SYSTEM);
+
         assertFalse(dpm.isAdminActiveAsUser(admin1, UserHandle.USER_SYSTEM));
 
         // ACTION_DEVICE_OWNER_CHANGED should be sent twice, once for setting the device owner
@@ -1044,6 +1178,8 @@
         // Check
         assertFalse(dpm.isProfileOwnerApp(admin1.getPackageName()));
         assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
+        verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+                null, DpmMockContext.CALLER_USER_HANDLE);
     }
 
     public void testSetProfileOwner_failures() throws Exception {
@@ -3885,6 +4021,8 @@
         dpm.setPasswordMinimumNumeric(admin1, 1);
         dpm.setPasswordMinimumSymbols(admin1, 0);
 
+        reset(mContext.spiedContext);
+
         PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics(
                 DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
                 8, 2,
@@ -3934,9 +4072,16 @@
         dpm.setActivePasswordState(passwordMetrics, userHandle);
         dpm.reportPasswordChanged(userHandle);
 
+        // Drain ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED broadcasts as part of
+        // reportPasswordChanged()
+        verify(mContext.spiedContext, times(3)).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+                MockUtils.checkUserHandle(userHandle));
+
         final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED);
         intent.setComponent(admin1);
-        intent.putExtra(Intent.EXTRA_USER, UserHandle.of(mContext.binder.callingUid));
+        intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userHandle));
 
         verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
                 MockUtils.checkIntent(intent),
@@ -4347,6 +4492,45 @@
         verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(null, caller);
     }
 
+    public void testDisallowSharingIntoProfileSetRestriction() {
+        Bundle restriction = new Bundle();
+        restriction.putBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, true);
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        RestrictionsListener listener = new RestrictionsListener(mContext);
+        listener.onUserRestrictionsChanged(DpmMockContext.CALLER_USER_HANDLE, restriction,
+                new Bundle());
+        verifyDataSharingChangedBroadcast();
+    }
+
+    public void testDisallowSharingIntoProfileClearRestriction() {
+        Bundle restriction = new Bundle();
+        restriction.putBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, true);
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        RestrictionsListener listener = new RestrictionsListener(mContext);
+        listener.onUserRestrictionsChanged(DpmMockContext.CALLER_USER_HANDLE, new Bundle(),
+                restriction);
+        verifyDataSharingChangedBroadcast();
+    }
+
+    public void testDisallowSharingIntoProfileUnchanged() {
+        RestrictionsListener listener = new RestrictionsListener(mContext);
+        listener.onUserRestrictionsChanged(DpmMockContext.CALLER_USER_HANDLE, new Bundle(),
+                new Bundle());
+        verify(mContext.spiedContext, never()).sendBroadcastAsUser(any(), any());
+    }
+
+    private void verifyDataSharingChangedBroadcast() {
+        Intent expectedIntent = new Intent(
+                DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_CHANGED);
+        expectedIntent.setPackage("com.android.managedprovisioning");
+        expectedIntent.putExtra(Intent.EXTRA_USER_ID, DpmMockContext.CALLER_USER_HANDLE);
+        verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
+                MockUtils.checkIntent(expectedIntent),
+                MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
+    }
+
     private void verifyCanGetOwnerInstalledCaCerts(
             final ComponentName caller, final DpmMockContext callerContext) throws Exception {
         final String alias = "cert";
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 4232c44..268d424 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -25,10 +25,12 @@
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
+import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
 import android.app.IActivityManager;
 import android.app.NotificationManager;
 import android.app.backup.IBackupManager;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -73,6 +75,7 @@
     public final SystemPropertiesForMock systemProperties;
     public final UserManager userManager;
     public final UserManagerInternal userManagerInternal;
+    public final UsageStatsManagerInternal usageStatsManagerInternal;
     public final PackageManagerInternal packageManagerInternal;
     public final UserManagerForMock userManagerForMock;
     public final PowerManagerForMock powerManager;
@@ -82,6 +85,7 @@
     public final IIpConnectivityMetrics iipConnectivityMetrics;
     public final IWindowManager iwindowManager;
     public final IActivityManager iactivityManager;
+    public ActivityManagerInternal activityManagerInternal;
     public final IPackageManager ipackageManager;
     public final IBackupManager ibackupManager;
     public final IAudioService iaudioService;
@@ -108,6 +112,7 @@
         systemProperties = mock(SystemPropertiesForMock.class);
         userManager = mock(UserManager.class);
         userManagerInternal = mock(UserManagerInternal.class);
+        usageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
         userManagerForMock = mock(UserManagerForMock.class);
         packageManagerInternal = mock(PackageManagerInternal.class);
         powerManager = mock(PowerManagerForMock.class);
@@ -117,6 +122,7 @@
         iipConnectivityMetrics = mock(IIpConnectivityMetrics.class);
         iwindowManager = mock(IWindowManager.class);
         iactivityManager = mock(IActivityManager.class);
+        activityManagerInternal = mock(ActivityManagerInternal.class);
         ipackageManager = mock(IPackageManager.class);
         ibackupManager = mock(IBackupManager.class);
         iaudioService = mock(IAudioService.class);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index e43786c..dec962e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -24,13 +24,16 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.util.ArraySet;
 
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
-import org.mockito.Mockito;
 import org.mockito.hamcrest.MockitoHamcrest;
 
+import java.util.Arrays;
+import java.util.Set;
+
 public class MockUtils {
     private MockUtils() {
     }
@@ -88,7 +91,8 @@
             @Override
             public boolean matches(Object item) {
                 if (item == null) return false;
-                return intent.filterEquals((Intent) item);
+                if (!intent.filterEquals((Intent) item)) return false;
+                return intent.getExtras().kindofEquals(((Intent) item).getExtras());
             }
             @Override
             public void describeTo(Description description) {
@@ -115,6 +119,30 @@
         return MockitoHamcrest.argThat(m);
     }
 
+    public static Set<String> checkAdminApps(String... adminApps) {
+        final Matcher<Set<String>> m = new BaseMatcher<Set<String>>() {
+            @Override
+            public boolean matches(Object item) {
+                if (item == null) return false;
+                final Set<String> actualAdminApps = (Set<String>) item;
+                if (adminApps.length != actualAdminApps.size()) {
+                    return false;
+                }
+                final Set<String> copyOfAdmins = new ArraySet<>(actualAdminApps);
+                for (String adminApp : adminApps) {
+                    copyOfAdmins.remove(adminApp);
+                }
+                return copyOfAdmins.isEmpty();
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("Admin apps=" + Arrays.toString(adminApps));
+            }
+        };
+        return MockitoHamcrest.argThat(m);
+    }
+
     private static String getRestrictionsAsString(Bundle b) {
         final StringBuilder sb = new StringBuilder();
         sb.append("[");
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index 08edd52..edc7d74 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -447,21 +447,24 @@
     @Test
     public void testParcelUnParcel() {
         Parcel parcel = Parcel.obtain();
-        BrightnessChangeEvent event = new BrightnessChangeEvent();
-        event.brightness = 23f;
-        event.timeStamp = 345L;
-        event.packageName = "com.example";
-        event.userId = 12;
-        event.luxValues = new float[2];
-        event.luxValues[0] = 3000.0f;
-        event.luxValues[1] = 4000.0f;
-        event.luxTimestamps = new long[2];
-        event.luxTimestamps[0] = 325L;
-        event.luxTimestamps[1] = 315L;
-        event.batteryLevel = 0.7f;
-        event.nightMode = false;
-        event.colorTemperature = 345;
-        event.lastBrightness = 50f;
+        BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
+        builder.setBrightness(23f);
+        builder.setTimeStamp(345L);
+        builder.setPackageName("com.example");
+        builder.setUserId(12);
+        float[] luxValues = new float[2];
+        luxValues[0] = 3000.0f;
+        luxValues[1] = 4000.0f;
+        builder.setLuxValues(luxValues);
+        long[] luxTimestamps = new long[2];
+        luxTimestamps[0] = 325L;
+        luxTimestamps[1] = 315L;
+        builder.setLuxTimestamps(luxTimestamps);
+        builder.setBatteryLevel(0.7f);
+        builder.setNightMode(false);
+        builder.setColorTemperature(345);
+        builder.setLastBrightness(50f);
+        BrightnessChangeEvent event = builder.build();
 
         event.writeToParcel(parcel, 0);
         byte[] parceled = parcel.marshall();
@@ -485,7 +488,8 @@
         assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA);
 
         parcel = Parcel.obtain();
-        event.batteryLevel = Float.NaN;
+        builder.setBatteryLevel(Float.NaN);
+        event = builder.build();
         event.writeToParcel(parcel, 0);
         parceled = parcel.marshall();
         parcel.recycle();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index f798e9c..9eb42e9 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -16,11 +16,11 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
+import static android.security.keystore.RecoveryMetadata.TYPE_LOCKSCREEN;
 
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD;
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PATTERN;
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PIN;
+import static android.security.keystore.RecoveryMetadata.TYPE_PASSWORD;
+import static android.security.keystore.RecoveryMetadata.TYPE_PATTERN;
+import static android.security.keystore.RecoveryMetadata.TYPE_PIN;
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
@@ -40,9 +40,9 @@
 import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
-import android.security.recoverablekeystore.KeyDerivationParameters;
-import android.security.recoverablekeystore.KeyEntryRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.security.keystore.KeyDerivationParams;
+import android.security.keystore.EntryRecoveryData;
+import android.security.keystore.RecoveryData;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -77,7 +77,8 @@
     private static final int TEST_USER_ID = 1000;
     private static final int TEST_RECOVERY_AGENT_UID = 10009;
     private static final int TEST_RECOVERY_AGENT_UID2 = 10010;
-    private static final long TEST_DEVICE_ID = 13295035643L;
+    private static final byte[] TEST_DEVICE_ID =
+            new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2};
     private static final String TEST_APP_KEY_ALIAS = "rcleaver";
     private static final int TEST_GENERATION_ID = 2;
     private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD;
@@ -239,7 +240,7 @@
     @Test
     public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception {
         SecretKey applicationKey = generateKey();
-        mRecoverableKeyStoreDb.setServerParameters(
+        mRecoverableKeyStoreDb.setServerParams(
                 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID);
         mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
         mRecoverableKeyStoreDb.insertKey(
@@ -275,7 +276,6 @@
 
     @Test
     public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception {
-
         mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
                 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
@@ -283,14 +283,14 @@
                 addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
         mKeySyncTask.run();
 
-        KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
-        KeyDerivationParameters keyDerivationParameters =
-                recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParameters();
-        assertThat(keyDerivationParameters.getAlgorithm()).isEqualTo(
-                KeyDerivationParameters.ALGORITHM_SHA256);
+        RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+        KeyDerivationParams KeyDerivationParams =
+                recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParams();
+        assertThat(KeyDerivationParams.getAlgorithm()).isEqualTo(
+                KeyDerivationParams.ALGORITHM_SHA256);
         verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
         byte[] lockScreenHash = KeySyncTask.hashCredentials(
-                keyDerivationParameters.getSalt(),
+                KeyDerivationParams.getSalt(),
                 TEST_CREDENTIAL);
         Long counterId = mRecoverableKeyStoreDb.getCounterId(TEST_USER_ID, TEST_RECOVERY_AGENT_UID);
         assertThat(counterId).isNotNull();
@@ -300,11 +300,11 @@
                 /*vaultParams=*/ KeySyncUtils.packVaultParams(
                         mKeyPair.getPublic(),
                         counterId,
-                        /*maxAttempts=*/ 10,
-                        TEST_DEVICE_ID));
-        List<KeyEntryRecoveryData> applicationKeys = recoveryData.getApplicationKeyBlobs();
+                        TEST_DEVICE_ID,
+                        /*maxAttempts=*/ 10));
+        List<EntryRecoveryData> applicationKeys = recoveryData.getEntryRecoveryData();
         assertThat(applicationKeys).hasSize(1);
-        KeyEntryRecoveryData keyData = applicationKeys.get(0);
+        EntryRecoveryData keyData = applicationKeys.get(0);
         assertEquals(TEST_APP_KEY_ALIAS, keyData.getAlias());
         assertThat(keyData.getAlias()).isEqualTo(keyData.getAlias());
         byte[] appKey = KeySyncUtils.decryptApplicationKey(
@@ -314,7 +314,6 @@
 
     @Test
     public void run_setsCorrectSnapshotVersion() throws Exception {
-
         mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
                 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
@@ -323,7 +322,7 @@
 
         mKeySyncTask.run();
 
-        KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+        RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
         assertThat(recoveryData.getSnapshotVersion()).isEqualTo(1); // default value;
         mRecoverableKeyStoreDb.setShouldCreateSnapshot(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, true);
 
@@ -353,9 +352,9 @@
 
         mKeySyncTask.run();
 
-        KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+        RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
         assertThat(recoveryData.getRecoveryMetadata()).hasSize(1);
-        assertThat(recoveryData.getRecoveryMetadata().get(1).getLockScreenUiFormat()).
+        assertThat(recoveryData.getRecoveryMetadata().get(0).getLockScreenUiFormat()).
                 isEqualTo(TYPE_PASSWORD);
     }
 
@@ -379,10 +378,10 @@
 
         mKeySyncTask.run();
 
-        KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+        RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
         assertThat(recoveryData.getRecoveryMetadata()).hasSize(1);
         // Password with only digits is changed to pin.
-        assertThat(recoveryData.getRecoveryMetadata().get(1).getLockScreenUiFormat()).
+        assertThat(recoveryData.getRecoveryMetadata().get(0).getLockScreenUiFormat()).
                 isEqualTo(TYPE_PIN);
     }
 
@@ -406,9 +405,9 @@
 
         mKeySyncTask.run();
 
-        KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+        RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
         assertThat(recoveryData.getRecoveryMetadata()).hasSize(1);
-        assertThat(recoveryData.getRecoveryMetadata().get(1).getLockScreenUiFormat()).
+        assertThat(recoveryData.getRecoveryMetadata().get(0).getLockScreenUiFormat()).
                 isEqualTo(TYPE_PATTERN);
     }
 
@@ -431,10 +430,10 @@
     @Test
     public void run_sendsEncryptedKeysOnlyForAgentWhichActiveUserSecretType() throws Exception {
         mRecoverableKeyStoreDb.setRecoverySecretTypes(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
-                new int[] {TYPE_LOCKSCREEN, 100});
+                new int[] {TYPE_LOCKSCREEN, 1000});
         // Snapshot will not be created during unlock event.
         mRecoverableKeyStoreDb.setRecoverySecretTypes(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2,
-                new int[] {100});
+                new int[] {1000});
 
         mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
                 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
@@ -471,7 +470,7 @@
     private SecretKey addApplicationKey(int userId, int recoveryAgentUid, String alias)
             throws Exception{
         SecretKey applicationKey = generateKey();
-        mRecoverableKeyStoreDb.setServerParameters(
+        mRecoverableKeyStoreDb.setServerParams(
                 userId, recoveryAgentUid, TEST_DEVICE_ID);
         mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, TEST_GENERATION_ID);
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
index 114da1a..3a9ff85 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
@@ -50,6 +50,8 @@
     private static final int RECOVERY_KEY_LENGTH_BITS = 256;
     private static final int THM_KF_HASH_SIZE = 256;
     private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
+    private static final byte[] TEST_DEVICE_ID =
+            new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2};
     private static final String SHA_256_ALGORITHM = "SHA-256";
     private static final String APPLICATION_KEY_ALGORITHM = "AES";
     private static final byte[] LOCK_SCREEN_HASH_1 =
@@ -348,8 +350,8 @@
         byte[] packedForm = KeySyncUtils.packVaultParams(
                 thmPublicKey,
                 /*counterId=*/ 1001L,
-                /*maxAttempts=*/ 10,
-                /*deviceId=*/ 1L);
+                TEST_DEVICE_ID,
+                /*maxAttempts=*/ 10);
 
         assertEquals(VAULT_PARAMS_LENGTH_BYTES, packedForm.length);
     }
@@ -361,8 +363,8 @@
         byte[] packedForm = KeySyncUtils.packVaultParams(
                 thmPublicKey,
                 /*counterId=*/ 1001L,
-                /*maxAttempts=*/ 10,
-                /*deviceId=*/ 1L);
+                TEST_DEVICE_ID,
+                /*maxAttempts=*/ 10);
 
         assertArrayEquals(
                 SecureBox.encodePublicKey(thmPublicKey),
@@ -376,8 +378,8 @@
         byte[] packedForm = KeySyncUtils.packVaultParams(
                 SecureBox.genKeyPair().getPublic(),
                 counterId,
-                /*maxAttempts=*/ 10,
-                /*deviceId=*/ 1L);
+                TEST_DEVICE_ID,
+                /*maxAttempts=*/ 10);
 
         ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
                 .order(ByteOrder.LITTLE_ENDIAN);
@@ -387,18 +389,17 @@
 
     @Test
     public void packVaultParams_encodesDeviceIdAsThirdParam() throws Exception {
-        long deviceId = 102942158152L;
 
         byte[] packedForm = KeySyncUtils.packVaultParams(
                 SecureBox.genKeyPair().getPublic(),
                 /*counterId=*/ 10021L,
-                /*maxAttempts=*/ 10,
-                deviceId);
+                TEST_DEVICE_ID,
+                /*maxAttempts=*/ 10);
 
         ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
                 .order(ByteOrder.LITTLE_ENDIAN);
         byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES);
-        assertEquals(deviceId, byteBuffer.getLong());
+        assertEquals(/* default value*/0, byteBuffer.getLong());
     }
 
     @Test
@@ -408,11 +409,12 @@
         byte[] packedForm = KeySyncUtils.packVaultParams(
                 SecureBox.genKeyPair().getPublic(),
                 /*counterId=*/ 1001L,
-                maxAttempts,
-                /*deviceId=*/ 1L);
+                TEST_DEVICE_ID,
+                maxAttempts);
 
         ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
                 .order(ByteOrder.LITTLE_ENDIAN);
+        // TODO: update position.
         byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + 2 * Long.BYTES);
         assertEquals(maxAttempts, byteBuffer.getInt());
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index ac2d36b..1bdcf47 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -16,8 +16,8 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+import static android.security.keystore.RecoveryMetadata.TYPE_LOCKSCREEN;
+import static android.security.keystore.RecoveryMetadata.TYPE_PASSWORD;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertArrayEquals;
@@ -35,18 +35,19 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.Manifest;
 import android.os.Binder;
 import android.os.ServiceSpecificException;
 import android.os.UserHandle;
 import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
-import android.security.recoverablekeystore.KeyDerivationParameters;
-import android.security.recoverablekeystore.KeyEntryRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
-import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
-import android.support.test.InstrumentationRegistry;
+import android.security.keystore.KeyDerivationParams;
+import android.security.keystore.EntryRecoveryData;
+import android.security.keystore.RecoveryMetadata;
+import android.security.keystore.RecoveryManager;
 import android.support.test.filters.SmallTest;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
@@ -78,6 +79,7 @@
 public class RecoverableKeyStoreManagerTest {
     private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
 
+    private static final String ROOT_CERTIFICATE_ALIAS = "put_default_alias_here";
     private static final String TEST_SESSION_ID = "karlin";
     private static final byte[] TEST_PUBLIC_KEY = new byte[] {
         (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
@@ -206,10 +208,9 @@
     }
 
     @Test
-    public void removeKey_UpdatesShouldCreateSnapshot() throws Exception {
+    public void removeKey_updatesShouldCreateSnapshot() throws Exception {
         int uid = Binder.getCallingUid();
         int userId = UserHandle.getCallingUserId();
-
         mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
         // Pretend that key was synced
         mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
@@ -220,6 +221,29 @@
     }
 
     @Test
+    public void removeKey_failureDoesNotUpdateShouldCreateSnapshot() throws Exception {
+        int uid = Binder.getCallingUid();
+        int userId = UserHandle.getCallingUserId();
+        mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
+        // Key did not exist
+        mRecoverableKeyStoreManager.removeKey(TEST_ALIAS);
+
+        assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
+    }
+
+    @Test
+    public void initRecoveryService_updatesShouldCreateSnapshot() throws Exception {
+        int uid = Binder.getCallingUid();
+        int userId = UserHandle.getCallingUserId();
+        // Sync is not needed.
+        mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
+
+        mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, TEST_PUBLIC_KEY);
+
+        assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
+    }
+
+    @Test
     public void startRecoverySession_checksPermissionFirst() throws Exception {
         mRecoverableKeyStoreManager.startRecoverySession(
                 TEST_SESSION_ID,
@@ -227,15 +251,15 @@
                 TEST_VAULT_PARAMS,
                 TEST_VAULT_CHALLENGE,
                 ImmutableList.of(
-                        new KeyStoreRecoveryMetadata(
+                        new RecoveryMetadata(
                                 TYPE_LOCKSCREEN,
                                 TYPE_PASSWORD,
-                                KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                                KeyDerivationParams.createSha256Params(TEST_SALT),
                                 TEST_SECRET)));
 
         verify(mMockContext, times(1))
                 .enforceCallingOrSelfPermission(
-                        eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE), any());
+                        eq(Manifest.permission.RECOVER_KEYSTORE), any());
     }
 
     @Test
@@ -246,10 +270,10 @@
                 TEST_VAULT_PARAMS,
                 TEST_VAULT_CHALLENGE,
                 ImmutableList.of(
-                        new KeyStoreRecoveryMetadata(
+                        new RecoveryMetadata(
                                 TYPE_LOCKSCREEN,
                                 TYPE_PASSWORD,
-                                KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                                KeyDerivationParams.createSha256Params(TEST_SALT),
                                 TEST_SECRET)));
 
         assertEquals(1, mRecoverySessionStorage.size());
@@ -271,7 +295,7 @@
             fail("should have thrown");
         } catch (ServiceSpecificException e) {
             assertThat(e.getMessage()).startsWith(
-                    "Only a single KeyStoreRecoveryMetadata is supported");
+                    "Only a single RecoveryMetadata is supported");
         }
     }
 
@@ -284,10 +308,10 @@
                     TEST_VAULT_PARAMS,
                     TEST_VAULT_CHALLENGE,
                     ImmutableList.of(
-                            new KeyStoreRecoveryMetadata(
+                            new RecoveryMetadata(
                                     TYPE_LOCKSCREEN,
                                     TYPE_PASSWORD,
-                                    KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                                    KeyDerivationParams.createSha256Params(TEST_SALT),
                                     TEST_SECRET)));
             fail("should have thrown");
         } catch (ServiceSpecificException e) {
@@ -306,10 +330,10 @@
                     vaultParams,
                     TEST_VAULT_CHALLENGE,
                     ImmutableList.of(
-                            new KeyStoreRecoveryMetadata(
+                            new RecoveryMetadata(
                                     TYPE_LOCKSCREEN,
                                     TYPE_PASSWORD,
-                                    KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                                    KeyDerivationParams.createSha256Params(TEST_SALT),
                                     TEST_SECRET)));
             fail("should have thrown");
         } catch (ServiceSpecificException e) {
@@ -324,7 +348,7 @@
                     TEST_SESSION_ID,
                     /*recoveryKeyBlob=*/ randomBytes(32),
                     /*applicationKeys=*/ ImmutableList.of(
-                            new KeyEntryRecoveryData("alias", randomBytes(32))
+                            new EntryRecoveryData("alias", randomBytes(32))
                     ));
             fail("should have thrown");
         } catch (ServiceSpecificException e) {
@@ -339,10 +363,10 @@
                 TEST_PUBLIC_KEY,
                 TEST_VAULT_PARAMS,
                 TEST_VAULT_CHALLENGE,
-                ImmutableList.of(new KeyStoreRecoveryMetadata(
+                ImmutableList.of(new RecoveryMetadata(
                         TYPE_LOCKSCREEN,
                         TYPE_PASSWORD,
-                        KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                        KeyDerivationParams.createSha256Params(TEST_SALT),
                         TEST_SECRET)));
 
         try {
@@ -363,17 +387,17 @@
                 TEST_PUBLIC_KEY,
                 TEST_VAULT_PARAMS,
                 TEST_VAULT_CHALLENGE,
-                ImmutableList.of(new KeyStoreRecoveryMetadata(
+                ImmutableList.of(new RecoveryMetadata(
                         TYPE_LOCKSCREEN,
                         TYPE_PASSWORD,
-                        KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                        KeyDerivationParams.createSha256Params(TEST_SALT),
                         TEST_SECRET)));
         byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
                 .getKeyClaimant();
         SecretKey recoveryKey = randomRecoveryKey();
         byte[] encryptedClaimResponse = encryptClaimResponse(
                 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
-        KeyEntryRecoveryData badApplicationKey = new KeyEntryRecoveryData(
+        EntryRecoveryData badApplicationKey = new EntryRecoveryData(
                 TEST_ALIAS,
                 randomBytes(32));
 
@@ -395,10 +419,10 @@
                 TEST_PUBLIC_KEY,
                 TEST_VAULT_PARAMS,
                 TEST_VAULT_CHALLENGE,
-                ImmutableList.of(new KeyStoreRecoveryMetadata(
+                ImmutableList.of(new RecoveryMetadata(
                         TYPE_LOCKSCREEN,
                         TYPE_PASSWORD,
-                        KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                        KeyDerivationParams.createSha256Params(TEST_SALT),
                         TEST_SECRET)));
         byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
                 .getKeyClaimant();
@@ -406,7 +430,7 @@
         byte[] encryptedClaimResponse = encryptClaimResponse(
                 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
         byte[] applicationKeyBytes = randomBytes(32);
-        KeyEntryRecoveryData applicationKey = new KeyEntryRecoveryData(
+        EntryRecoveryData applicationKey = new EntryRecoveryData(
                 TEST_ALIAS,
                 encryptedApplicationKey(recoveryKey, applicationKeyBytes));
 
@@ -449,6 +473,20 @@
     }
 
     @Test
+    public void setRecoverySecretTypes_updatesShouldCreateSnapshot() throws Exception {
+        int uid = Binder.getCallingUid();
+        int userId = UserHandle.getCallingUserId();
+        int[] types = new int[]{1, 2, 3};
+
+        mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
+        // Pretend that key was synced
+        mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
+        mRecoverableKeyStoreManager.setRecoverySecretTypes(types);
+
+        assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
+    }
+
+    @Test
     public void setRecoveryStatus_forOneAlias() throws Exception {
         int userId = UserHandle.getCallingUserId();
         int uid = Binder.getCallingUid();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index b8080ab..5cb7b67 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -29,7 +29,7 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.security.keystore.RecoveryManager;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -51,6 +51,12 @@
     private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
     private File mDatabaseFile;
 
+    private static final byte[] SERVER_PARAMS =
+            new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2};
+
+    private static final byte[] SERVER_PARAMS2 =
+            new byte[]{1, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4};
+
     @Before
     public void setUp() {
         Context context = InstrumentationRegistry.getTargetContext();
@@ -277,8 +283,7 @@
 
         Map<String, Integer> statuses = mRecoverableKeyStoreDb.getStatusForAllKeys(uid);
         assertThat(statuses).hasSize(3);
-        assertThat(statuses).containsEntry(alias,
-                RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS);
+        assertThat(statuses).containsEntry(alias, RecoveryManager.RECOVERY_STATUS_SYNC_IN_PROGRESS);
         assertThat(statuses).containsEntry(alias2, status);
         assertThat(statuses).containsEntry(alias3, status);
 
@@ -329,8 +334,7 @@
         int uid = 10009;
         assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
 
-        long serverParams = 123456L;
-        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+        mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS);
         assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
     }
 
@@ -438,32 +442,30 @@
 
         PublicKey pubkey1 = genRandomPublicKey();
         int[] types1 = new int[]{1};
-        long serverParams1 = 111L;
 
         PublicKey pubkey2 = genRandomPublicKey();
         int[] types2 = new int[]{2};
-        long serverParams2 = 222L;
 
         mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
         mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1);
-        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1);
+        mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS);
 
         assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
                 types1);
-        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
-                serverParams1);
+        assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(
+                SERVER_PARAMS);
         assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
                 pubkey1);
 
         // Check that the methods don't interfere with each other.
         mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
         mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2);
-        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2);
+        mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS2);
 
         assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
                 types2);
-        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
-                serverParams2);
+        assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(
+                SERVER_PARAMS2);
         assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
                 pubkey2);
     }
@@ -480,35 +482,33 @@
     }
 
     @Test
-    public void setServerParameters_replaceOldValue() throws Exception {
+    public void setServerParams_replaceOldValue() throws Exception {
         int userId = 12;
         int uid = 10009;
-        long serverParams1 = 111L;
-        long serverParams2 = 222L;
-        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1);
-        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2);
-        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
-                serverParams2);
+
+        mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS);
+        mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS2);
+        assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(
+                SERVER_PARAMS2);
     }
 
     @Test
-    public void getServerParameters_returnsNullIfNoValue() throws Exception {
+    public void getServerParams_returnsNullIfNoValue() throws Exception {
         int userId = 12;
         int uid = 10009;
-        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+        assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isNull();
 
         PublicKey pubkey = genRandomPublicKey();
         mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey);
-        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+        assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isNull();
     }
 
     @Test
-    public void getServerParameters_returnsInsertedValue() throws Exception {
+    public void getServerParams_returnsInsertedValue() throws Exception {
         int userId = 12;
         int uid = 10009;
-        long serverParams = 123456L;
-        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
-        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+        mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS);
+        assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(SERVER_PARAMS);
     }
 
     @Test
@@ -591,22 +591,21 @@
         int uid = 10009;
         PublicKey pubkey1 = genRandomPublicKey();
         PublicKey pubkey2 = genRandomPublicKey();
-        long serverParams = 123456L;
 
         mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
         assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
                 pubkey1);
-        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+        assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isNull();
 
-        mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+        mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS);
         assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
                 pubkey1);
-        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+        assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(SERVER_PARAMS);
 
         mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
         assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
                 pubkey2);
-        assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+        assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(SERVER_PARAMS);
     }
 
     private static byte[] getUtf8Bytes(String s) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java
index 2759e39..6308f74 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java
@@ -3,7 +3,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.security.keystore.RecoveryData;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -26,7 +26,7 @@
     @Test
     public void get_returnsSetSnapshot() {
         int userId = 1000;
-        KeyStoreRecoveryData recoveryData = new KeyStoreRecoveryData(
+        RecoveryData recoveryData = new RecoveryData(
                 /*snapshotVersion=*/ 1,
                 new ArrayList<>(),
                 new ArrayList<>(),
@@ -39,7 +39,7 @@
     @Test
     public void remove_removesSnapshots() {
         int userId = 1000;
-        KeyStoreRecoveryData recoveryData = new KeyStoreRecoveryData(
+        RecoveryData recoveryData = new RecoveryData(
                 /*snapshotVersion=*/ 1,
                 new ArrayList<>(),
                 new ArrayList<>(),
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index b073ee5..0abb48f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -294,6 +294,7 @@
                 null /*disabledPkg*/,
                 null /*sharedUser*/,
                 UPDATED_CODE_PATH /*codePath*/,
+                null /*resourcePath*/,
                 null /*legacyNativeLibraryPath*/,
                 "arm64-v8a" /*primaryCpuAbi*/,
                 "armeabi" /*secondaryCpuAbi*/,
@@ -327,6 +328,7 @@
                 null /*disabledPkg*/,
                 null /*sharedUser*/,
                 UPDATED_CODE_PATH /*codePath*/,
+                null /*resourcePath*/,
                 null /*legacyNativeLibraryPath*/,
                 "arm64-v8a" /*primaryCpuAbi*/,
                 "armeabi" /*secondaryCpuAbi*/,
@@ -367,6 +369,7 @@
                     null /*disabledPkg*/,
                     testUserSetting01 /*sharedUser*/,
                     UPDATED_CODE_PATH /*codePath*/,
+                    null /*resourcePath*/,
                     null /*legacyNativeLibraryPath*/,
                     "arm64-v8a" /*primaryCpuAbi*/,
                     "armeabi" /*secondaryCpuAbi*/,
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 32b0b26..49601c3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -34,7 +34,6 @@
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
 import java.nio.charset.StandardCharsets;
-import java.security.cert.Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -262,14 +261,13 @@
         assertBundleApproximateEquals(a.mAppMetaData, b.mAppMetaData);
         assertEquals(a.mVersionName, b.mVersionName);
         assertEquals(a.mSharedUserId, b.mSharedUserId);
-        assertTrue(Arrays.equals(a.mSignatures, b.mSignatures));
-        assertTrue(Arrays.equals(a.mCertificates, b.mCertificates));
+        assertTrue(Arrays.equals(a.mSigningDetails.signatures, b.mSigningDetails.signatures));
         assertTrue(Arrays.equals(a.mLastPackageUsageTimeInMills, b.mLastPackageUsageTimeInMills));
         assertEquals(a.mExtras, b.mExtras);
         assertEquals(a.mRestrictedAccountType, b.mRestrictedAccountType);
         assertEquals(a.mRequiredAccountType, b.mRequiredAccountType);
         assertEquals(a.mOverlayTarget, b.mOverlayTarget);
-        assertEquals(a.mSigningKeys, b.mSigningKeys);
+        assertEquals(a.mSigningDetails.publicKeys, b.mSigningDetails.publicKeys);
         assertEquals(a.mUpgradeKeySets, b.mUpgradeKeySets);
         assertEquals(a.mKeySetMapping, b.mKeySetMapping);
         assertEquals(a.cpuAbiOverride, b.cpuAbiOverride);
@@ -495,14 +493,16 @@
         pkg.mAppMetaData = new Bundle();
         pkg.mVersionName = "foo17";
         pkg.mSharedUserId = "foo18";
-        pkg.mSignatures = new Signature[] { new Signature(new byte[16]) };
-        pkg.mCertificates = new Certificate[][] { new Certificate[] { null }};
+        pkg.mSigningDetails =
+                new PackageParser.SigningDetails(
+                        new Signature[] { new Signature(new byte[16]) },
+                        2,
+                        new ArraySet<>());
         pkg.mExtras = new Bundle();
         pkg.mRestrictedAccountType = "foo19";
         pkg.mRequiredAccountType = "foo20";
         pkg.mOverlayTarget = "foo21";
         pkg.mOverlayPriority = 100;
-        pkg.mSigningKeys = new ArraySet<>();
         pkg.mUpgradeKeySets = new ArraySet<>();
         pkg.mKeySetMapping = new ArrayMap<>();
         pkg.cpuAbiOverride = "foo22";
diff --git a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
index 62f7cd0..a628b7b 100644
--- a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
+++ b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.IApplicationToken;
@@ -254,4 +255,9 @@
     public boolean canAcquireSleepToken() {
         throw new UnsupportedOperationException("not implemented");
     }
+
+    @Override
+    public void writeIdentifierToProto(ProtoOutputStream proto, long fieldId){
+        throw new UnsupportedOperationException("not implemented");
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
index 9a6da0e..1ad73cf 100644
--- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
@@ -19,6 +19,7 @@
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
+import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
@@ -128,7 +129,7 @@
     }
 
     @Test
-    public void layoutWindowLw_withDisplayCutout_fullscreen() {
+    public void layoutWindowLw_withDisplayCutout_layoutFullscreen() {
         addDisplayCutout();
 
         mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
@@ -137,6 +138,22 @@
         mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
         mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
 
+        assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0);
+        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_fullscreen() {
+        addDisplayCutout();
+
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
+        mPolicy.addWindow(mAppWindow);
+
+        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
         assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0);
         assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
         assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
@@ -147,7 +164,7 @@
     public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() {
         addDisplayCutout();
 
-        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
         mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
         mPolicy.addWindow(mAppWindow);
 
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
index e7e9abad..ad89953 100644
--- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
@@ -24,6 +24,8 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 
+import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
 
@@ -31,6 +33,8 @@
 import android.content.ContextWrapper;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Path;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.IBinder;
@@ -38,6 +42,7 @@
 import android.support.test.InstrumentationRegistry;
 import android.testing.TestableResources;
 import android.view.Display;
+import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.View;
@@ -65,6 +70,9 @@
 
     FakeWindowState mStatusBar;
     FakeWindowState mNavigationBar;
+    private boolean mHasDisplayCutout;
+    private int mRotation = ROTATION_0;
+    private final Matrix mTmpMatrix = new Matrix();
 
     @Before
     public void setUpBase() throws Exception {
@@ -80,16 +88,32 @@
 
         mPolicy = TestablePhoneWindowManager.create(mContext);
 
-        setRotation(ROTATION_0);
+        updateDisplayFrames();
     }
 
     public void setRotation(int rotation) {
+        mRotation = rotation;
+        updateDisplayFrames();
+    }
+
+    private void updateDisplayFrames() {
         DisplayInfo info = new DisplayInfo();
 
-        final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270;
+        final boolean flippedDimensions = mRotation == ROTATION_90 || mRotation == ROTATION_270;
         info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
         info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
-        info.rotation = rotation;
+        info.rotation = mRotation;
+        if (mHasDisplayCutout) {
+            Path p = new Path();
+            p.addRect(DISPLAY_WIDTH / 4, 0, DISPLAY_WIDTH * 3 / 4, DISPLAY_CUTOUT_HEIGHT,
+                    Path.Direction.CCW);
+            transformPhysicalToLogicalCoordinates(
+                    mRotation, DISPLAY_WIDTH, DISPLAY_HEIGHT, mTmpMatrix);
+            p.transform(mTmpMatrix);
+            info.displayCutout = DisplayCutout.fromBounds(p);
+        } else {
+            info.displayCutout = null;
+        }
 
         mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info);
     }
@@ -116,7 +140,8 @@
     }
 
     public void addDisplayCutout() {
-        mPolicy.mEmulateDisplayCutout = true;
+        mHasDisplayCutout = true;
+        updateDisplayFrames();
     }
 
     /** Asserts that {@code actual} is inset by the given amounts from the full display rect. */
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 725fb21..40964c0 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -34,13 +34,13 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
 import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManager;
 import android.appwidget.AppWidgetManager;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -55,6 +55,7 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
 import android.view.Display;
 
 import com.android.server.SystemService;
@@ -65,7 +66,9 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Unit test for AppStandbyController.
@@ -78,6 +81,11 @@
     private static final String PACKAGE_1 = "com.example.foo";
     private static final int UID_1 = 10000;
     private static final int USER_ID = 0;
+    private static final int USER_ID2 = 10;
+
+    private static final String ADMIN_PKG = "com.android.admin";
+    private static final String ADMIN_PKG2 = "com.android.admin2";
+    private static final String ADMIN_PKG3 = "com.android.admin3";
 
     private static final long MINUTE_MS = 60 * 1000;
     private static final long HOUR_MS = 60 * MINUTE_MS;
@@ -454,6 +462,105 @@
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
                 REASON_PREDICTED, mInjector.mElapsedRealtime);
         assertBucket(STANDBY_BUCKET_FREQUENT);
+    }
 
+    @Test
+    public void testAddActiveDeviceAdmin() {
+        assertActiveAdmins(USER_ID, (String[]) null);
+        assertActiveAdmins(USER_ID2, (String[]) null);
+
+        mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID);
+        assertActiveAdmins(USER_ID, ADMIN_PKG);
+        assertActiveAdmins(USER_ID2, (String[]) null);
+
+        mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID);
+        assertActiveAdmins(USER_ID, ADMIN_PKG);
+        assertActiveAdmins(USER_ID2, (String[]) null);
+
+        mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2);
+        assertActiveAdmins(USER_ID, ADMIN_PKG);
+        assertActiveAdmins(USER_ID2, ADMIN_PKG2);
+    }
+
+    @Test
+    public void testSetActiveAdminApps() {
+        assertActiveAdmins(USER_ID, (String[]) null);
+        assertActiveAdmins(USER_ID2, (String[]) null);
+
+        setActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2);
+        assertActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2);
+        assertActiveAdmins(USER_ID2, (String[]) null);
+
+        mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2);
+        setActiveAdmins(USER_ID2, ADMIN_PKG);
+        assertActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2);
+        assertActiveAdmins(USER_ID2, ADMIN_PKG);
+
+        mController.setActiveAdminApps(null, USER_ID);
+        assertActiveAdmins(USER_ID, (String[]) null);
+    }
+
+    @Test
+    public void isActiveDeviceAdmin() {
+        assertActiveAdmins(USER_ID, (String[]) null);
+        assertActiveAdmins(USER_ID2, (String[]) null);
+
+        mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID);
+        assertIsActiveAdmin(ADMIN_PKG, USER_ID);
+        assertIsNotActiveAdmin(ADMIN_PKG, USER_ID2);
+
+        mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2);
+        mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID2);
+        assertIsActiveAdmin(ADMIN_PKG, USER_ID);
+        assertIsNotActiveAdmin(ADMIN_PKG2, USER_ID);
+        assertIsActiveAdmin(ADMIN_PKG, USER_ID2);
+        assertIsActiveAdmin(ADMIN_PKG2, USER_ID2);
+
+        setActiveAdmins(USER_ID2, ADMIN_PKG2);
+        assertIsActiveAdmin(ADMIN_PKG2, USER_ID2);
+        assertIsNotActiveAdmin(ADMIN_PKG, USER_ID2);
+        assertIsActiveAdmin(ADMIN_PKG, USER_ID);
+        assertIsNotActiveAdmin(ADMIN_PKG2, USER_ID);
+    }
+
+    private String getAdminAppsStr(int userId) {
+        return getAdminAppsStr(userId, mController.getActiveAdminAppsForTest(userId));
+    }
+
+    private String getAdminAppsStr(int userId, Set<String> adminApps) {
+        return "admin apps for u" + userId + ": "
+                + (adminApps == null ? "null" : Arrays.toString(adminApps.toArray()));
+    }
+
+    private void assertIsActiveAdmin(String adminApp, int userId) {
+        assertTrue(adminApp + " should be an active admin; " + getAdminAppsStr(userId),
+                mController.isActiveDeviceAdmin(adminApp, userId));
+    }
+
+    private void assertIsNotActiveAdmin(String adminApp, int userId) {
+        assertFalse(adminApp + " shouldn't be an active admin; " + getAdminAppsStr(userId),
+                mController.isActiveDeviceAdmin(adminApp, userId));
+    }
+
+    private void assertActiveAdmins(int userId, String... admins) {
+        final Set<String> actualAdminApps = mController.getActiveAdminAppsForTest(userId);
+        if (admins == null) {
+            if (actualAdminApps != null && !actualAdminApps.isEmpty()) {
+                fail("Admin apps should be null; " + getAdminAppsStr(userId, actualAdminApps));
+            }
+            return;
+        }
+        assertEquals("No. of admin apps not equal; " + getAdminAppsStr(userId, actualAdminApps)
+                + "; expected=" + Arrays.toString(admins), admins.length, actualAdminApps.size());
+        final Set<String> adminAppsCopy = new ArraySet<>(actualAdminApps);
+        for (String admin : admins) {
+            adminAppsCopy.remove(admin);
+        }
+        assertTrue("Unexpected admin apps; " + getAdminAppsStr(userId, actualAdminApps)
+                + "; expected=" + Arrays.toString(admins), adminAppsCopy.isEmpty());
+    }
+
+    private void setActiveAdmins(int userId, String... admins) {
+        mController.setActiveAdminApps(new ArraySet<>(Arrays.asList(admins)), userId);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index a317706..5ed17cc 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -416,7 +416,8 @@
     }
 
     @Override
-    public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback) {
+    public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback,
+            CharSequence message) {
     }
 
     @Override
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 0c7397a..c532a8a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -107,6 +107,68 @@
     }
 
     @Test
+    public void testAlarmsOnly_alarmMediaMuteNotApplied() {
+        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
+        mZenModeHelperSpy.mConfig.allowAlarms = false;
+        mZenModeHelperSpy.mConfig.allowMediaSystemOther = false;
+        assertFalse(mZenModeHelperSpy.mConfig.allowAlarms);
+        assertFalse(mZenModeHelperSpy.mConfig.allowMediaSystemOther);
+        mZenModeHelperSpy.applyRestrictions();
+
+        // Alarms only mode will not silence alarms
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+                AudioAttributes.USAGE_ALARM);
+
+        // Alarms only mode will not silence media
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+                AudioAttributes.USAGE_MEDIA);
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+                AudioAttributes.USAGE_GAME);
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+                AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+                AudioAttributes.USAGE_UNKNOWN);
+    }
+
+    @Test
+    public void testAlarmsOnly_callsMuteApplied() {
+        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
+        mZenModeHelperSpy.mConfig.allowCalls = true;
+        assertTrue(mZenModeHelperSpy.mConfig.allowCalls);
+        mZenModeHelperSpy.applyRestrictions();
+
+        // Alarms only mode will silence calls despite priority-mode config
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
+                AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
+                AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST);
+    }
+
+    @Test
+    public void testAlarmsOnly_allZenConfigToggledCannotBypass_alarmMuteNotApplied() {
+        // Only audio attributes with SUPPRESIBLE_NEVER can bypass
+        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
+        mZenModeHelperSpy.mConfig.allowAlarms = false;
+        mZenModeHelperSpy.mConfig.allowMediaSystemOther = false;
+        mZenModeHelperSpy.mConfig.allowReminders = false;
+        mZenModeHelperSpy.mConfig.allowCalls = false;
+        mZenModeHelperSpy.mConfig.allowMessages = false;
+        mZenModeHelperSpy.mConfig.allowEvents = false;
+        mZenModeHelperSpy.mConfig.allowRepeatCallers= false;
+        assertFalse(mZenModeHelperSpy.mConfig.allowAlarms);
+        assertFalse(mZenModeHelperSpy.mConfig.allowMediaSystemOther);
+        assertFalse(mZenModeHelperSpy.mConfig.allowReminders);
+        assertFalse(mZenModeHelperSpy.mConfig.allowCalls);
+        assertFalse(mZenModeHelperSpy.mConfig.allowMessages);
+        assertFalse(mZenModeHelperSpy.mConfig.allowEvents);
+        assertFalse(mZenModeHelperSpy.mConfig.allowRepeatCallers);
+        mZenModeHelperSpy.applyRestrictions();
+
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+                AudioAttributes.USAGE_ALARM);
+    }
+
+    @Test
     public void testZenAllCannotBypass() {
         // Only audio attributes with SUPPRESIBLE_NEVER can bypass
         mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index 2ec218a..ff3d586 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -33,7 +33,6 @@
 
 import android.app.ActivityManager;
 import android.app.AppGlobals;
-import android.app.admin.DevicePolicyManager;
 import android.app.usage.UsageStatsManager.StandbyBuckets;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
@@ -66,13 +65,16 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
+import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
@@ -87,6 +89,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Manages the standby state of an app, listening to various events.
@@ -147,6 +150,9 @@
     @GuardedBy("mAppIdleLock")
     private List<String> mCarrierPrivilegedApps;
 
+    @GuardedBy("mActiveAdminApps")
+    private final SparseArray<Set<String>> mActiveAdminApps = new SparseArray<>();
+
     // Messages for the handler
     static final int MSG_INFORM_LISTENERS = 3;
     static final int MSG_FORCE_IDLE_STATE = 4;
@@ -619,6 +625,9 @@
     public void onUserRemoved(int userId) {
         synchronized (mAppIdleLock) {
             mAppIdleHistory.onUserRemoved(userId);
+            synchronized (mActiveAdminApps) {
+                mActiveAdminApps.remove(userId);
+            }
         }
     }
 
@@ -857,10 +866,39 @@
                 newBucket);
     }
 
-    private boolean isActiveDeviceAdmin(String packageName, int userId) {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        if (dpm == null) return false;
-        return dpm.packageHasActiveAdmins(packageName, userId);
+    @VisibleForTesting
+    boolean isActiveDeviceAdmin(String packageName, int userId) {
+        synchronized (mActiveAdminApps) {
+            final Set<String> adminPkgs = mActiveAdminApps.get(userId);
+            return adminPkgs != null && adminPkgs.contains(packageName);
+        }
+    }
+
+    public void addActiveDeviceAdmin(String adminPkg, int userId) {
+        synchronized (mActiveAdminApps) {
+            Set<String> adminPkgs = mActiveAdminApps.get(userId);
+            if (adminPkgs == null) {
+                adminPkgs = new ArraySet<>();
+                mActiveAdminApps.put(userId, adminPkgs);
+            }
+            adminPkgs.add(adminPkg);
+        }
+    }
+
+    public void setActiveAdminApps(Set<String> adminPkgs, int userId) {
+        synchronized (mActiveAdminApps) {
+            if (adminPkgs == null) {
+                mActiveAdminApps.remove(userId);
+            } else {
+                mActiveAdminApps.put(userId, adminPkgs);
+            }
+        }
+    }
+
+    Set<String> getActiveAdminAppsForTest(int userId) {
+        synchronized (mActiveAdminApps) {
+            return mActiveAdminApps.get(userId);
+        }
     }
 
     /**
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 463a26e..78cc81f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -70,6 +70,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * A service that collects, aggregates, and persists application usage data.
@@ -1020,5 +1021,14 @@
         public long getTimeSinceLastJobRun(String packageName, int userId) {
             return mAppStandby.getTimeSinceLastJobRun(packageName, userId);
         }
+
+        public void onActiveAdminAdded(String packageName, int userId) {
+            mAppStandby.addActiveDeviceAdmin(packageName, userId);
+        }
+
+        @Override
+        public void setActiveAdminApps(Set<String> packageNames, int userId) {
+            mAppStandby.setActiveAdminApps(packageNames, userId);
+        }
     }
 }
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 2091101..8c7d6b3 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -1408,7 +1408,7 @@
      * @param extras Bundle containing extra information associated with the event.
      */
     public void sendCallEvent(String event, Bundle extras) {
-        mInCallAdapter.sendCallEvent(mTelecomCallId, event, extras);
+        mInCallAdapter.sendCallEvent(mTelecomCallId, event, mTargetSdkVersion, extras);
     }
 
     /**
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 4bc2a9b..658685f 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -286,11 +286,12 @@
      *
      * @param callId The callId to send the event for.
      * @param event The event.
+     * @param targetSdkVer Target sdk version of the app calling this api
      * @param extras Extras associated with the event.
      */
-    public void sendCallEvent(String callId, String event, Bundle extras) {
+    public void sendCallEvent(String callId, String event, int targetSdkVer, Bundle extras) {
         try {
-            mAdapter.sendCallEvent(callId, event, extras);
+            mAdapter.sendCallEvent(callId, event, targetSdkVer, extras);
         } catch (RemoteException ignored) {
         }
     }
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index bddf3ea..d292db3 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -1440,6 +1441,13 @@
     public void addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras) {
         try {
             if (isServiceConnected()) {
+                if (extras != null && extras.getBoolean(EXTRA_IS_HANDOVER) &&
+                        mContext.getApplicationContext().getApplicationInfo().targetSdkVersion >
+                                Build.VERSION_CODES.O_MR1) {
+                    Log.e("TAG", "addNewIncomingCall failed. Use public api " +
+                            "acceptHandover for API > O-MR1");
+                    // TODO add "return" after DUO team adds support for new handover API
+                }
                 getTelecomService().addNewIncomingCall(
                         phoneAccount, extras == null ? new Bundle() : extras);
             }
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index 23ac940..87ccd3e 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -64,7 +64,7 @@
 
     void pullExternalCall(String callId);
 
-    void sendCallEvent(String callId, String event, in Bundle extras);
+    void sendCallEvent(String callId, String event, int targetSdkVer, in Bundle extras);
 
     void putExtras(String callId, in Bundle extras);
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index fccc2a6..5a1a3e3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -337,6 +337,19 @@
             "notify_handover_video_from_wifi_to_lte_bool";
 
     /**
+     * Flag specifying whether the carrier wants to notify the user when a VT call has been handed
+     * over from LTE to WIFI.
+     * <p>
+     * The handover notification is sent as a
+     * {@link TelephonyManager#EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI}
+     * {@link android.telecom.Connection} event, which an {@link android.telecom.InCallService}
+     * should use to trigger the display of a user-facing message.
+     * @hide
+     */
+    public static final String KEY_NOTIFY_HANDOVER_VIDEO_FROM_LTE_TO_WIFI_BOOL =
+            "notify_handover_video_from_lte_to_wifi_bool";
+
+    /**
      * Flag specifying whether the carrier supports downgrading a video call (tx, rx or tx/rx)
      * directly to an audio call.
      * @hide
@@ -1740,6 +1753,13 @@
     public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL =
             "check_pricing_with_carrier_data_roaming_bool";
 
+    /**
+     * List of thresholds of RSRP for determining the display level of LTE signal bar.
+     * @hide
+     */
+    public static final String KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY =
+            "lte_rsrp_thresholds_int_array";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -1756,6 +1776,7 @@
         sDefaults.putBoolean(KEY_CARRIER_VOLTE_AVAILABLE_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_VT_AVAILABLE_BOOL, false);
         sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false);
+        sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_LTE_TO_WIFI_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL, true);
         sDefaults.putString(KEY_DEFAULT_VM_NUMBER_STRING, "");
         sDefaults.putBoolean(KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL, false);
@@ -2028,6 +2049,15 @@
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false);
         sDefaults.putBoolean(KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL, false);
+        sDefaults.putIntArray(KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                        -140, /* SIGNAL_STRENGTH_NONE_OR_UNKNOWN */
+                        -128, /* SIGNAL_STRENGTH_POOR */
+                        -118, /* SIGNAL_STRENGTH_MODERATE */
+                        -108, /* SIGNAL_STRENGTH_GOOD */
+                        -98,  /* SIGNAL_STRENGTH_GREAT */
+                        -44
+                });
     }
 
     /**
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index c7e5131..98ea451 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -244,6 +244,13 @@
      */
     public static final int LISTEN_DATA_ACTIVATION_STATE                   = 0x00040000;
 
+    /**
+     *  Listen for changes to the user mobile data state
+     *
+     *  @see #onUserMobileDataStateChanged
+     */
+    public static final int LISTEN_USER_MOBILE_DATA_STATE                  = 0x00080000;
+
      /*
      * Subscription used to listen to the phone state changes
      * @hide
@@ -349,6 +356,9 @@
                     case LISTEN_DATA_ACTIVATION_STATE:
                         PhoneStateListener.this.onDataActivationStateChanged((int)msg.obj);
                         break;
+                    case LISTEN_USER_MOBILE_DATA_STATE:
+                        PhoneStateListener.this.onUserMobileDataStateChanged((boolean)msg.obj);
+                        break;
                     case LISTEN_CARRIER_NETWORK_CHANGE:
                         PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj);
                         break;
@@ -543,6 +553,14 @@
     }
 
     /**
+     * Callback invoked when the user mobile data state has changed
+     * @param enabled indicates whether the current user mobile data state is enabled or disabled.
+     */
+    public void onUserMobileDataStateChanged(boolean enabled) {
+        // default implementation empty
+    }
+
+    /**
      * Callback invoked when telephony has received notice from a carrier
      * app that a network action that could result in connectivity loss
      * has been requested by an app using
@@ -654,6 +672,10 @@
             send(LISTEN_DATA_ACTIVATION_STATE, 0, 0, activationState);
         }
 
+        public void onUserMobileDataStateChanged(boolean enabled) {
+            send(LISTEN_USER_MOBILE_DATA_STATE, 0, 0, enabled);
+        }
+
         public void onCarrierNetworkChange(boolean active) {
             send(LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active);
         }
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index de02de7..d2134f9 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -19,9 +19,13 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.CarrierConfigManager;
 import android.util.Log;
 import android.content.res.Resources;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+
 /**
  * Contains phone signal strength related information.
  */
@@ -51,6 +55,8 @@
     //Use int max, as -1 is a valid value in signal strength
     public static final int INVALID = 0x7FFFFFFF;
 
+    private static final int LTE_RSRP_THRESHOLDS_NUM = 6;
+
     private int mGsmSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5
     private int mGsmBitErrorRate;   // bit error rate (0-7, 99) as defined in TS 27.007 8.5
     private int mCdmaDbm;   // This value is the RSSI value
@@ -70,6 +76,9 @@
     private boolean isGsm; // This value is set by the ServiceStateTracker onSignalStrengthResult
     private boolean mUseOnlyRsrpForLteLevel; // Use only RSRP for the number of LTE signal bar.
 
+    // The threshold of LTE RSRP for determining the display level of LTE signal bar.
+    private int mLteRsrpThresholds[] = new int[LTE_RSRP_THRESHOLDS_NUM];
+
     /**
      * Create a new SignalStrength from a intent notifier Bundle
      *
@@ -110,6 +119,7 @@
         mTdScdmaRscp = INVALID;
         isGsm = true;
         mUseOnlyRsrpForLteLevel = false;
+        setLteRsrpThresholds(getDefaultLteRsrpThresholds());
     }
 
     /**
@@ -137,6 +147,7 @@
         mTdScdmaRscp = INVALID;
         isGsm = gsmFlag;
         mUseOnlyRsrpForLteLevel = false;
+        setLteRsrpThresholds(getDefaultLteRsrpThresholds());
     }
 
     /**
@@ -276,6 +287,8 @@
         mTdScdmaRscp = INVALID;
         isGsm = gsm;
         mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
+
+        setLteRsrpThresholds(getDefaultLteRsrpThresholds());
         if (DBG) log("initialize: " + toString());
     }
 
@@ -299,6 +312,7 @@
         mTdScdmaRscp = s.mTdScdmaRscp;
         isGsm = s.isGsm;
         mUseOnlyRsrpForLteLevel = s.mUseOnlyRsrpForLteLevel;
+        setLteRsrpThresholds(s.mLteRsrpThresholds);
     }
 
     /**
@@ -325,6 +339,9 @@
         mTdScdmaRscp = in.readInt();
         isGsm = (in.readInt() != 0);
         mUseOnlyRsrpForLteLevel = (in.readInt() != 0);
+        for (int i = 0; i < LTE_RSRP_THRESHOLDS_NUM; i++) {
+            mLteRsrpThresholds[i] = in.readInt();
+        }
     }
 
     /**
@@ -374,6 +391,9 @@
         out.writeInt(mTdScdmaRscp);
         out.writeInt(isGsm ? 1 : 0);
         out.writeInt(mUseOnlyRsrpForLteLevel ? 1 : 0);
+        for (int i = 0; i < LTE_RSRP_THRESHOLDS_NUM; i++) {
+            out.writeInt(mLteRsrpThresholds[i]);
+        }
     }
 
     /**
@@ -480,6 +500,22 @@
     }
 
     /**
+     * Sets the threshold array for determining the display level of LTE signal bar.
+     *
+     * @param lteRsrpThresholds int array for determining the display level.
+     *
+     * @hide
+     */
+    public void setLteRsrpThresholds(int[] lteRsrpThresholds) {
+        if ((lteRsrpThresholds == null)
+                || (lteRsrpThresholds.length != LTE_RSRP_THRESHOLDS_NUM)) {
+            Log.wtf(LOG_TAG, "setLteRsrpThresholds - lteRsrpThresholds is invalid.");
+            return;
+        }
+        System.arraycopy(lteRsrpThresholds, 0, mLteRsrpThresholds, 0, LTE_RSRP_THRESHOLDS_NUM);
+    }
+
+    /**
      * Get the GSM Signal Strength, valid values are (0-31, 99) as defined in TS
      * 27.007 8.5
      */
@@ -833,25 +869,18 @@
          */
         int rssiIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN, rsrpIconLevel = -1, snrIconLevel = -1;
 
-        int[] threshRsrp = Resources.getSystem().getIntArray(
-                com.android.internal.R.array.config_lteDbmThresholds);
-        if (threshRsrp.length != 6) {
-            Log.wtf(LOG_TAG, "getLteLevel - config_lteDbmThresholds has invalid num of elements."
-                    + " Cannot evaluate RSRP signal.");
-        } else {
-            if (mLteRsrp > threshRsrp[5]) {
-                rsrpIconLevel = -1;
-            } else if (mLteRsrp >= (threshRsrp[4] - mLteRsrpBoost)) {
-                rsrpIconLevel = SIGNAL_STRENGTH_GREAT;
-            } else if (mLteRsrp >= (threshRsrp[3] - mLteRsrpBoost)) {
-                rsrpIconLevel = SIGNAL_STRENGTH_GOOD;
-            } else if (mLteRsrp >= (threshRsrp[2] - mLteRsrpBoost)) {
-                rsrpIconLevel = SIGNAL_STRENGTH_MODERATE;
-            } else if (mLteRsrp >= (threshRsrp[1] - mLteRsrpBoost)) {
-                rsrpIconLevel = SIGNAL_STRENGTH_POOR;
-            } else if (mLteRsrp >= threshRsrp[0]) {
-                rsrpIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
-            }
+        if (mLteRsrp > mLteRsrpThresholds[5]) {
+            rsrpIconLevel = -1;
+        } else if (mLteRsrp >= (mLteRsrpThresholds[4] - mLteRsrpBoost)) {
+            rsrpIconLevel = SIGNAL_STRENGTH_GREAT;
+        } else if (mLteRsrp >= (mLteRsrpThresholds[3] - mLteRsrpBoost)) {
+            rsrpIconLevel = SIGNAL_STRENGTH_GOOD;
+        } else if (mLteRsrp >= (mLteRsrpThresholds[2] - mLteRsrpBoost)) {
+            rsrpIconLevel = SIGNAL_STRENGTH_MODERATE;
+        } else if (mLteRsrp >= (mLteRsrpThresholds[1] - mLteRsrpBoost)) {
+            rsrpIconLevel = SIGNAL_STRENGTH_POOR;
+        } else if (mLteRsrp >= mLteRsrpThresholds[0]) {
+            rsrpIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
         }
 
         if (useOnlyRsrpForLteLevel()) {
@@ -1010,7 +1039,7 @@
                 + (mLteSignalStrength * primeNum) + (mLteRsrp * primeNum)
                 + (mLteRsrq * primeNum) + (mLteRssnr * primeNum) + (mLteCqi * primeNum)
                 + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)
-                + (mUseOnlyRsrpForLteLevel ? 1 : 0));
+                + (mUseOnlyRsrpForLteLevel ? 1 : 0) + (Arrays.hashCode(mLteRsrpThresholds)));
     }
 
     /**
@@ -1045,7 +1074,8 @@
                 && mLteRsrpBoost == s.mLteRsrpBoost
                 && mTdScdmaRscp == s.mTdScdmaRscp
                 && isGsm == s.isGsm
-                && mUseOnlyRsrpForLteLevel == s.mUseOnlyRsrpForLteLevel);
+                && mUseOnlyRsrpForLteLevel == s.mUseOnlyRsrpForLteLevel
+                && Arrays.equals(mLteRsrpThresholds, s.mLteRsrpThresholds));
     }
 
     /**
@@ -1070,7 +1100,8 @@
                 + " " + mTdScdmaRscp
                 + " " + (isGsm ? "gsm|lte" : "cdma")
                 + " " + (mUseOnlyRsrpForLteLevel ? "use_only_rsrp_for_lte_level" :
-                         "use_rsrp_and_rssnr_for_lte_level"));
+                         "use_rsrp_and_rssnr_for_lte_level")
+                + " " + (Arrays.toString(mLteRsrpThresholds)));
     }
 
     /** Returns the signal strength related to GSM. */
@@ -1126,6 +1157,10 @@
         mTdScdmaRscp = m.getInt("TdScdma");
         isGsm = m.getBoolean("isGsm");
         mUseOnlyRsrpForLteLevel = m.getBoolean("useOnlyRsrpForLteLevel");
+        ArrayList<Integer> lteRsrpThresholds = m.getIntegerArrayList("lteRsrpThresholds");
+        for (int i = 0; i < lteRsrpThresholds.size(); i++) {
+            mLteRsrpThresholds[i] = lteRsrpThresholds.get(i);
+        }
     }
 
     /**
@@ -1151,6 +1186,21 @@
         m.putInt("TdScdma", mTdScdmaRscp);
         m.putBoolean("isGsm", isGsm);
         m.putBoolean("useOnlyRsrpForLteLevel", mUseOnlyRsrpForLteLevel);
+        ArrayList<Integer> lteRsrpThresholds = new ArrayList<Integer>();
+        for (int value : mLteRsrpThresholds) {
+            lteRsrpThresholds.add(value);
+        }
+        m.putIntegerArrayList("lteRsrpThresholds", lteRsrpThresholds);
+    }
+
+    /**
+     * Gets the default threshold array for determining the display level of LTE signal bar.
+     *
+     * @return int array for determining the display level.
+     */
+    private int[] getDefaultLteRsrpThresholds() {
+        return CarrierConfigManager.getDefaultConfig().getIntArray(
+                CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY);
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index af5b190..1945e5d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -53,6 +53,7 @@
 
 import com.android.ims.internal.IImsMMTelFeature;
 import com.android.ims.internal.IImsRcsFeature;
+import com.android.ims.internal.IImsRegistration;
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.ITelecomService;
@@ -831,6 +832,17 @@
             "android.telephony.event.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE";
 
     /**
+     * {@link android.telecom.Connection} event used to indicate that an IMS call has be
+     * successfully handed over from LTE to WIFI.
+     * <p>
+     * Sent via {@link android.telecom.Connection#sendConnectionEvent(String, Bundle)}.
+     * The {@link Bundle} parameter is expected to be null when this connection event is used.
+     * @hide
+     */
+    public static final String EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI =
+            "android.telephony.event.EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI";
+
+    /**
      * {@link android.telecom.Connection} event used to indicate that an IMS call failed to be
      * handed over from LTE to WIFI.
      * <p>
@@ -4921,6 +4933,25 @@
     }
 
     /**
+     * @return the {@IImsRegistration} interface that corresponds with the slot index and feature.
+     * @param slotIndex The SIM slot corresponding to the ImsService ImsRegistration is active for.
+     * @param feature An integer indicating the feature that we wish to get the ImsRegistration for.
+     * Corresponds to features defined in ImsFeature.
+     * @hide
+     */
+    public @Nullable IImsRegistration getImsRegistration(int slotIndex, int feature) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getImsRegistration(slotIndex, feature);
+            }
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "getImsRegistration, RemoteException: " + e.getMessage());
+        }
+        return null;
+    }
+
+    /**
      * Set IMS registration state
      *
      * @param Registration state
@@ -6253,8 +6284,10 @@
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      *
-     * @hide
+     * {@hide}
      **/
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void setSimPowerState(int state) {
         setSimPowerStateForSlot(getSlotIndex(), state);
     }
@@ -6273,8 +6306,10 @@
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      *
-     * @hide
+     * {@hide}
      **/
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void setSimPowerStateForSlot(int slotIndex, int state) {
         try {
             ITelephony telephony = getITelephony();
diff --git a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
index 8ed96a3..7eeb1ce 100644
--- a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
+++ b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
@@ -15,12 +15,10 @@
  */
 package android.telephony;
 
-import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
-
-import android.telecom.PhoneAccountHandle;
 import android.telephony.VisualVoicemailService.VisualVoicemailTask;
+
 import java.util.Collections;
 import java.util.List;
 
@@ -75,6 +73,7 @@
         private String mClientPrefix = DEFAULT_CLIENT_PREFIX;
         private List<String> mOriginatingNumbers = DEFAULT_ORIGINATING_NUMBERS;
         private int mDestinationPort = DEFAULT_DESTINATION_PORT;
+        private String mPackageName;
 
         public VisualVoicemailSmsFilterSettings build() {
             return new VisualVoicemailSmsFilterSettings(this);
@@ -116,6 +115,15 @@
             return this;
         }
 
+        /**
+         * The package that registered this filter.
+         *
+         * @hide
+         */
+        public Builder setPackageName(String packageName) {
+            mPackageName = packageName;
+            return this;
+        }
     }
 
     /**
@@ -138,12 +146,20 @@
     public final int destinationPort;
 
     /**
+     * The package that registered this filter.
+     *
+     * @hide
+     */
+    public final String packageName;
+
+    /**
      * Use {@link Builder} to construct
      */
     private VisualVoicemailSmsFilterSettings(Builder builder) {
         clientPrefix = builder.mClientPrefix;
         originatingNumbers = builder.mOriginatingNumbers;
         destinationPort = builder.mDestinationPort;
+        packageName = builder.mPackageName;
     }
 
     public static final Creator<VisualVoicemailSmsFilterSettings> CREATOR =
@@ -154,7 +170,7 @@
                     builder.setClientPrefix(in.readString());
                     builder.setOriginatingNumbers(in.createStringArrayList());
                     builder.setDestinationPort(in.readInt());
-
+                    builder.setPackageName(in.readString());
                     return builder.build();
                 }
 
@@ -174,10 +190,11 @@
         dest.writeString(clientPrefix);
         dest.writeStringList(originatingNumbers);
         dest.writeInt(destinationPort);
+        dest.writeString(packageName);
     }
 
     @Override
-    public String toString(){
+    public String toString() {
         return "[VisualVoicemailSmsFilterSettings "
                 + "clientPrefix=" + clientPrefix
                 + ", originatingNumbers=" + originatingNumbers
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index da51c86..ef3a183 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.net.LinkAddress;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -40,7 +41,7 @@
     private final int mActive;
     private final String mType;
     private final String mIfname;
-    private final List<InterfaceAddress> mAddresses;
+    private final List<LinkAddress> mAddresses;
     private final List<InetAddress> mDnses;
     private final List<InetAddress> mGateways;
     private final List<String> mPcscfs;
@@ -71,7 +72,7 @@
      */
     public DataCallResponse(int status, int suggestedRetryTime, int cid, int active,
                             @Nullable String type, @Nullable String ifname,
-                            @Nullable List<InterfaceAddress> addresses,
+                            @Nullable List<LinkAddress> addresses,
                             @Nullable List<InetAddress> dnses,
                             @Nullable List<InetAddress> gateways,
                             @Nullable List<String> pcscfs, int mtu) {
@@ -96,7 +97,7 @@
         mType = source.readString();
         mIfname = source.readString();
         mAddresses = new ArrayList<>();
-        source.readList(mAddresses, InterfaceAddress.class.getClassLoader());
+        source.readList(mAddresses, LinkAddress.class.getClassLoader());
         mDnses = new ArrayList<>();
         source.readList(mDnses, InetAddress.class.getClassLoader());
         mGateways = new ArrayList<>();
@@ -140,10 +141,10 @@
     public String getIfname() { return mIfname; }
 
     /**
-     * @return A list of {@link InterfaceAddress}
+     * @return A list of {@link LinkAddress}
      */
     @NonNull
-    public List<InterfaceAddress> getAddresses() { return mAddresses; }
+    public List<LinkAddress> getAddresses() { return mAddresses; }
 
     /**
      * @return A list of DNS server addresses, e.g., "192.0.1.3" or
diff --git a/telephony/java/android/telephony/data/InterfaceAddress.java b/telephony/java/android/telephony/data/InterfaceAddress.java
deleted file mode 100644
index 00d212a..0000000
--- a/telephony/java/android/telephony/data/InterfaceAddress.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 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 android.telephony.data;
-
-import android.annotation.SystemApi;
-import android.net.NetworkUtils;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-/**
- * This class represents a Network Interface address. In short it's an IP address, a subnet mask
- * when the address is an IPv4 one. An IP address and a network prefix length in the case of IPv6
- * address.
- *
- * @hide
- */
-@SystemApi
-public final class InterfaceAddress implements Parcelable {
-
-    private final InetAddress mInetAddress;
-
-    private final int mPrefixLength;
-
-    /**
-     * @param inetAddress A {@link InetAddress} of the address
-     * @param prefixLength The network prefix length for this address.
-     */
-    public InterfaceAddress(InetAddress inetAddress, int prefixLength) {
-        mInetAddress = inetAddress;
-        mPrefixLength = prefixLength;
-    }
-
-    /**
-     * @param address The address in string format
-     * @param prefixLength The network prefix length for this address.
-     * @throws UnknownHostException
-     */
-    public InterfaceAddress(String address, int prefixLength) throws UnknownHostException {
-        InetAddress ia;
-        try {
-            ia = NetworkUtils.numericToInetAddress(address);
-        } catch (IllegalArgumentException e) {
-            throw new UnknownHostException("Non-numeric ip addr=" + address);
-        }
-        mInetAddress = ia;
-        mPrefixLength = prefixLength;
-    }
-
-    public InterfaceAddress(Parcel source) {
-        mInetAddress = (InetAddress) source.readSerializable();
-        mPrefixLength = source.readInt();
-    }
-
-    /**
-     * @return an InetAddress for this address.
-     */
-    public InetAddress getAddress() { return mInetAddress; }
-
-    /**
-     * @return The network prefix length for this address.
-     */
-    public int getNetworkPrefixLength() { return mPrefixLength; }
-
-    @Override
-    public boolean equals (Object o) {
-        if (this == o) return true;
-
-        if (o == null || !(o instanceof InterfaceAddress)) {
-            return false;
-        }
-
-        InterfaceAddress other = (InterfaceAddress) o;
-        return this.mInetAddress.equals(other.mInetAddress)
-                && this.mPrefixLength == other.mPrefixLength;
-    }
-
-    @Override
-    public int hashCode() {
-        return mInetAddress.hashCode() * 31 + mPrefixLength * 37;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public String toString() {
-        return mInetAddress + "/" + mPrefixLength;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeSerializable(mInetAddress);
-        dest.writeInt(mPrefixLength);
-    }
-
-    public static final Parcelable.Creator<InterfaceAddress> CREATOR =
-            new Parcelable.Creator<InterfaceAddress>() {
-        @Override
-        public InterfaceAddress createFromParcel(Parcel source) {
-            return new InterfaceAddress(source);
-        }
-
-        @Override
-        public InterfaceAddress[] newArray(int size) {
-            return new InterfaceAddress[size];
-        }
-    };
-}
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
new file mode 100644
index 0000000..29849c1
--- /dev/null
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+package android.telephony.euicc;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.euicc.EuiccProfileInfo;
+import android.util.Log;
+
+import com.android.internal.telephony.euicc.IEuiccCardController;
+import com.android.internal.telephony.euicc.IGetAllProfilesCallback;
+
+/**
+ * EuiccCardManager is the application interface to an eSIM card.
+ *
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+public class EuiccCardManager {
+    private static final String TAG = "EuiccCardManager";
+
+    /** Result code of execution with no error. */
+    public static final int RESULT_OK = 0;
+
+    /**
+     * Callback to receive the result of an eUICC card API.
+     *
+     * @param <T> Type of the result.
+     */
+    public interface ResultCallback<T> {
+        /**
+         * This method will be called when an eUICC card API call is completed.
+         *
+         * @param resultCode This can be {@link #RESULT_OK} or other positive values returned by the
+         *     eUICC.
+         * @param result The result object. It can be null if the {@code resultCode} is not
+         *     {@link #RESULT_OK}.
+         */
+        void onComplete(int resultCode, T result);
+    }
+
+    private final Context mContext;
+
+    /** @hide */
+    public EuiccCardManager(Context context) {
+        mContext = context;
+    }
+
+    private IEuiccCardController getIEuiccCardController() {
+        return IEuiccCardController.Stub.asInterface(
+                ServiceManager.getService("euicc_card_controller"));
+    }
+
+    /**
+     * Gets all the profiles on eUicc.
+     *
+     * @param callback the callback to get the result code and all the profiles.
+     */
+    public void getAllProfiles(ResultCallback<EuiccProfileInfo[]> callback) {
+        try {
+            getIEuiccCardController().getAllProfiles(mContext.getOpPackageName(),
+                    new IGetAllProfilesCallback.Stub() {
+                        @Override
+                        public void onComplete(int resultCode, EuiccProfileInfo[] profiles) {
+                            callback.onComplete(resultCode, profiles);
+                        }
+                    });
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling getAllProfiles", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/euicc/EuiccNotification.java b/telephony/java/android/telephony/euicc/EuiccNotification.java
new file mode 100644
index 0000000..ef3c1ce
--- /dev/null
+++ b/telephony/java/android/telephony/euicc/EuiccNotification.java
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+package android.telephony.euicc;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * This represents a signed notification which is defined in SGP.22. It can be either a profile
+ * installation result or a notification generated for profile operations (e.g., enabling,
+ * disabling, or deleting).
+ *
+ * @hide
+ *
+ * TODO(b/35851809): Make this a @SystemApi.
+ */
+public class EuiccNotification implements Parcelable {
+    /** Event */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "EVENT_" }, value = {
+            EVENT_INSTALL,
+            EVENT_ENABLE,
+            EVENT_DISABLE,
+            EVENT_DELETE
+    })
+    public @interface Event {}
+
+    /** A profile is downloaded and installed. */
+    public static final int EVENT_INSTALL = 1;
+
+    /** A profile is enabled. */
+    public static final int EVENT_ENABLE = 1 << 1;
+
+    /** A profile is disabled. */
+    public static final int EVENT_DISABLE = 1 << 2;
+
+    /** A profile is deleted. */
+    public static final int EVENT_DELETE = 1 << 3;
+
+    /** Value of the bits of all above events */
+    @Event
+    public static final int ALL_EVENTS =
+            EVENT_INSTALL | EVENT_ENABLE | EVENT_DISABLE | EVENT_DELETE;
+
+    private final int mSeq;
+    private final String mTargetAddr;
+    @Event private final int mEvent;
+    @Nullable private final byte[] mData;
+
+    /**
+     * Creates an instance.
+     *
+     * @param seq The sequence number of this notification.
+     * @param targetAddr The target server where to send this notification.
+     * @param event The event which causes this notification.
+     * @param data The data which needs to be sent to the target server. This can be null for
+     *     building a list of notification metadata without data.
+     */
+    public EuiccNotification(int seq, String targetAddr, @Event int event, @Nullable byte[] data) {
+        mSeq = seq;
+        mTargetAddr = targetAddr;
+        mEvent = event;
+        mData = data;
+    }
+
+    /** @return The sequence number of this notification. */
+    public int getSeq() {
+        return mSeq;
+    }
+
+    /** @return The target server address where this notification should be sent to. */
+    public String getTargetAddr() {
+        return mTargetAddr;
+    }
+
+    /** @return The event of this notification. */
+    @Event
+    public int getEvent() {
+        return mEvent;
+    }
+
+    /** @return The notification data which needs to be sent to the target server. */
+    @Nullable
+    public byte[] getData() {
+        return mData;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        EuiccNotification that = (EuiccNotification) obj;
+        return mSeq == that.mSeq
+                && Objects.equals(mTargetAddr, that.mTargetAddr)
+                && mEvent == that.mEvent
+                && Arrays.equals(mData, that.mData);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 1;
+        result = 31 * result + mSeq;
+        result = 31 * result + Objects.hashCode(mTargetAddr);
+        result = 31 * result + mEvent;
+        result = 31 * result + Arrays.hashCode(mData);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "EuiccNotification (seq="
+                + mSeq
+                + ", targetAddr="
+                + mTargetAddr
+                + ", event="
+                + mEvent
+                + ", data="
+                + (mData == null ? "null" : "byte[" + mData.length + "]")
+                + ")";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mSeq);
+        dest.writeString(mTargetAddr);
+        dest.writeInt(mEvent);
+        dest.writeByteArray(mData);
+    }
+
+    private EuiccNotification(Parcel source) {
+        mSeq = source.readInt();
+        mTargetAddr = source.readString();
+        mEvent = source.readInt();
+        mData = source.createByteArray();
+    }
+
+    public static final Creator<EuiccNotification> CREATOR =
+            new Creator<EuiccNotification>() {
+                @Override
+                public EuiccNotification createFromParcel(Parcel source) {
+                    return new EuiccNotification(source);
+                }
+
+                @Override
+                public EuiccNotification[] newArray(int size) {
+                    return new EuiccNotification[size];
+                }
+            };
+}
diff --git a/telephony/java/android/telephony/euicc/EuiccRat.java b/telephony/java/android/telephony/euicc/EuiccRat.java
new file mode 100644
index 0000000..6a56503a
--- /dev/null
+++ b/telephony/java/android/telephony/euicc/EuiccRat.java
@@ -0,0 +1,259 @@
+/*
+ * 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.
+ */
+package android.telephony.euicc;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.carrier.CarrierIdentifier;
+import android.service.euicc.EuiccProfileInfo;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * This represents the RAT (Rules Authorisation Table) stored on eUICC.
+ *
+ * @hide
+ *
+ * TODO(b/35851809): Make this a @SystemApi.
+ */
+public final class EuiccRat implements Parcelable {
+    /** Profile policy rule flags */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "POLICY_RULE_FLAG_" }, value = {
+            POLICY_RULE_FLAG_CONSENT_REQUIRED
+    })
+    public @interface PolicyRuleFlag {}
+
+    /** User consent is required to install the profile. */
+    public static final int POLICY_RULE_FLAG_CONSENT_REQUIRED = 1;
+
+    private final int[] mPolicyRules;
+    private final CarrierIdentifier[][] mCarrierIds;
+    private final int[] mPolicyRuleFlags;
+
+    /** This is used to build new {@link EuiccRat} instance. */
+    public static final class Builder {
+        private int[] mPolicyRules;
+        private CarrierIdentifier[][] mCarrierIds;
+        private int[] mPolicyRuleFlags;
+        private int mPosition;
+
+        /**
+         * Creates a new builder.
+         *
+         * @param ruleNum The number of authorisation rules in the table.
+         */
+        public Builder(int ruleNum) {
+            mPolicyRules = new int[ruleNum];
+            mCarrierIds = new CarrierIdentifier[ruleNum][];
+            mPolicyRuleFlags = new int[ruleNum];
+        }
+
+        /**
+         * Builds the RAT instance. This builder should not be used anymore after this method is
+         * called, otherwise {@link NullPointerException} will be thrown.
+         */
+        public EuiccRat build() {
+            if (mPosition != mPolicyRules.length) {
+                throw new IllegalStateException(
+                        "Not enough rules are added, expected: "
+                                + mPolicyRules.length
+                                + ", added: "
+                                + mPosition);
+            }
+            return new EuiccRat(mPolicyRules, mCarrierIds, mPolicyRuleFlags);
+        }
+
+        /**
+         * Adds an authorisation rule.
+         *
+         * @throws ArrayIndexOutOfBoundsException If the {@code mPosition} is larger than the size
+         *     this table.
+         */
+        public Builder add(int policyRules, CarrierIdentifier[] carrierId, int policyRuleFlags) {
+            if (mPosition >= mPolicyRules.length) {
+                throw new ArrayIndexOutOfBoundsException(mPosition);
+            }
+            mPolicyRules[mPosition] = policyRules;
+            mCarrierIds[mPosition] = carrierId;
+            mPolicyRuleFlags[mPosition] = policyRuleFlags;
+            mPosition++;
+            return this;
+        }
+    }
+
+    /**
+     * @param mccRule A 2-character or 3-character string which can be either MCC or MNC. The
+     *     character 'E' is used as a wild char to match any digit.
+     * @param mcc A 2-character or 3-character string which can be either MCC or MNC.
+     * @return Whether the {@code mccRule} matches {@code mcc}.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static boolean match(String mccRule, String mcc) {
+        if (mccRule.length() < mcc.length()) {
+            return false;
+        }
+        for (int i = 0; i < mccRule.length(); i++) {
+            // 'E' is the wild char to match any digit.
+            if (mccRule.charAt(i) == 'E'
+                    || (i < mcc.length() && mccRule.charAt(i) == mcc.charAt(i))) {
+                continue;
+            }
+            return false;
+        }
+        return true;
+    }
+
+    private EuiccRat(int[] policyRules, CarrierIdentifier[][] carrierIds, int[] policyRuleFlags) {
+        mPolicyRules = policyRules;
+        mCarrierIds = carrierIds;
+        mPolicyRuleFlags = policyRuleFlags;
+    }
+
+    /**
+     * Finds the index of the first authorisation rule matching the given policy and carrier id. If
+     * the returned index is not negative, the carrier is allowed to apply this policy to its
+     * profile.
+     *
+     * @param policy The policy rule.
+     * @param carrierId The carrier id.
+     * @return The index of authorization rule. If no rule is found, -1 will be returned.
+     */
+    public int findIndex(@EuiccProfileInfo.PolicyRule int policy, CarrierIdentifier carrierId) {
+        for (int i = 0; i < mPolicyRules.length; i++) {
+            if ((mPolicyRules[i] & policy) == 0) {
+                continue;
+            }
+            CarrierIdentifier[] carrierIds = mCarrierIds[i];
+            if (carrierIds == null || carrierIds.length == 0) {
+                continue;
+            }
+            for (int j = 0; j < carrierIds.length; j++) {
+                CarrierIdentifier ruleCarrierId = carrierIds[j];
+                if (!match(ruleCarrierId.getMcc(), carrierId.getMcc())
+                        || !match(ruleCarrierId.getMnc(), carrierId.getMnc())) {
+                    continue;
+                }
+                String gid = ruleCarrierId.getGid1();
+                if (!TextUtils.isEmpty(gid) && !gid.equals(carrierId.getGid1())) {
+                    continue;
+                }
+                gid = ruleCarrierId.getGid2();
+                if (!TextUtils.isEmpty(gid) && !gid.equals(carrierId.getGid2())) {
+                    continue;
+                }
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Tests if the entry in the table has the given policy rule flag.
+     *
+     * @param index The index of the entry.
+     * @param flag The policy rule flag to be tested.
+     * @throws ArrayIndexOutOfBoundsException If the {@code index} is negative or larger than the
+     *     size of this table.
+     */
+    public boolean hasPolicyRuleFlag(int index, @PolicyRuleFlag int flag) {
+        if (index < 0 || index >= mPolicyRules.length) {
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        return (mPolicyRuleFlags[index] & flag) != 0;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeIntArray(mPolicyRules);
+        for (CarrierIdentifier[] ids : mCarrierIds) {
+            dest.writeTypedArray(ids, flags);
+        }
+        dest.writeIntArray(mPolicyRuleFlags);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        EuiccRat that = (EuiccRat) obj;
+        if (mCarrierIds.length != that.mCarrierIds.length) {
+            return false;
+        }
+        for (int i = 0; i < mCarrierIds.length; i++) {
+            CarrierIdentifier[] carrierIds = mCarrierIds[i];
+            CarrierIdentifier[] thatCarrierIds = that.mCarrierIds[i];
+            if (carrierIds != null && thatCarrierIds != null) {
+                if (carrierIds.length != thatCarrierIds.length) {
+                    return false;
+                }
+                for (int j = 0; j < carrierIds.length; j++) {
+                    if (!carrierIds[j].equals(thatCarrierIds[j])) {
+                        return false;
+                    }
+                }
+                continue;
+            } else if (carrierIds == null && thatCarrierIds == null) {
+                continue;
+            }
+            return false;
+        }
+
+        return Arrays.equals(mPolicyRules, that.mPolicyRules)
+                && Arrays.equals(mPolicyRuleFlags, that.mPolicyRuleFlags);
+    }
+
+    private EuiccRat(Parcel source) {
+        mPolicyRules = source.createIntArray();
+        int len = mPolicyRules.length;
+        mCarrierIds = new CarrierIdentifier[len][];
+        for (int i = 0; i < len; i++) {
+            mCarrierIds[i] = source.createTypedArray(CarrierIdentifier.CREATOR);
+        }
+        mPolicyRuleFlags = source.createIntArray();
+    }
+
+    public static final Creator<EuiccRat> CREATOR =
+            new Creator<EuiccRat>() {
+                @Override
+                public EuiccRat createFromParcel(Parcel source) {
+                    return new EuiccRat(source);
+                }
+
+                @Override
+                public EuiccRat[] newArray(int size) {
+                    return new EuiccRat[size];
+                }
+            };
+}
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 8230eaf..aaa0f08 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -26,12 +26,14 @@
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MMTelFeature;
 import android.telephony.ims.feature.RcsFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.ims.internal.IImsMMTelFeature;
 import com.android.ims.internal.IImsRcsFeature;
+import com.android.ims.internal.IImsRegistration;
 import com.android.ims.internal.IImsServiceController;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -113,6 +115,12 @@
                 throws RemoteException {
             ImsService.this.removeImsFeature(slotId, featureType, c);
         }
+
+        @Override
+        public IImsRegistration getRegistration(int slotId) throws RemoteException {
+            ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId);
+            return r != null ? r.getBinder() : null;
+        }
     };
 
     /**
@@ -174,6 +182,8 @@
         f.setSlotId(slotId);
         f.addImsFeatureStatusCallback(c);
         addImsFeature(slotId, featureType, f);
+        // TODO: Remove once new onFeatureReady AIDL is merged in.
+        f.onFeatureReady();
     }
 
     private void addImsFeature(int slotId, int featureType, ImsFeature f) {
@@ -236,4 +246,13 @@
     public @Nullable RcsFeature onCreateRcsFeature(int slotId) {
         return null;
     }
+
+    /**
+     * @param slotId The slot that is associated with the IMS Registration.
+     * @return the ImsRegistration implementation associated with the slot.
+     * @hide
+     */
+    public ImsRegistrationImplBase getRegistration(int slotId) {
+        return new ImsRegistrationImplBase();
+    }
 }
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index ca4a210..d47cea30 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -96,7 +96,7 @@
             new WeakHashMap<IImsFeatureStatusCallback, Boolean>());
     private @ImsState int mState = STATE_NOT_AVAILABLE;
     private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-    private Context mContext;
+    protected Context mContext;
 
     public void setContext(Context context) {
         mContext = context;
diff --git a/telephony/java/android/telephony/ims/internal/ImsService.java b/telephony/java/android/telephony/ims/internal/ImsService.java
index b7c8ca0..afaf332 100644
--- a/telephony/java/android/telephony/ims/internal/ImsService.java
+++ b/telephony/java/android/telephony/ims/internal/ImsService.java
@@ -24,7 +24,6 @@
 import android.telephony.ims.internal.aidl.IImsConfig;
 import android.telephony.ims.internal.aidl.IImsMmTelFeature;
 import android.telephony.ims.internal.aidl.IImsRcsFeature;
-import android.telephony.ims.internal.aidl.IImsRegistration;
 import android.telephony.ims.internal.aidl.IImsServiceController;
 import android.telephony.ims.internal.aidl.IImsServiceControllerListener;
 import android.telephony.ims.internal.feature.ImsFeature;
@@ -32,11 +31,12 @@
 import android.telephony.ims.internal.feature.RcsFeature;
 import android.telephony.ims.internal.stub.ImsConfigImplBase;
 import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
-import android.telephony.ims.internal.stub.ImsRegistrationImplBase;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
+import com.android.ims.internal.IImsRegistration;
 import com.android.internal.annotations.VisibleForTesting;
 
 /**
diff --git a/telephony/java/android/telephony/ims/internal/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/SmsImplBase.java
index 47414cf..eb805a8 100644
--- a/telephony/java/android/telephony/ims/internal/SmsImplBase.java
+++ b/telephony/java/android/telephony/ims/internal/SmsImplBase.java
@@ -124,70 +124,79 @@
    * method should be implemented by the IMS providers to provide implementation of sending an SMS
    * over IMS.
    *
-   * @param smsc the Short Message Service Center address.
-   * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
-   * {@link SmsMessage#FORMAT_3GPP2}.
+   * @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
+   * @param smsc the Short Message Service Center address.
+   * {@link SmsMessage#FORMAT_3GPP2}.
    * @param isRetry whether it is a retry of an already attempted message or not.
    * @param pdu PDUs representing the contents of the message.
    */
-  public void sendSms(int messageRef, String format, String smsc, boolean isRetry, byte[] pdu) {
+  public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
+                      byte[] pdu) {
     // Base implementation returns error. Should be overridden.
     try {
-      onSendSmsResult(messageRef, SEND_STATUS_ERROR, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
+      onSendSmsResult(token, messageRef, SEND_STATUS_ERROR,
+              SmsManager.RESULT_ERROR_GENERIC_FAILURE);
     } catch (RemoteException e) {
       Log.e(LOG_TAG, "Can not send sms: " + e.getMessage());
     }
   }
 
   /**
-   * This method will be triggered by the platform after {@link #onSmsReceived(String, byte[])} has
-   * been called to deliver the result to the IMS provider.
+   * This method will be triggered by the platform after {@link #onSmsReceived(int, String, byte[])}
+   * has been called to deliver the result to the IMS provider.
    *
+   * @param token token provided in {@link #onSmsReceived(int, String, byte[])}
    * @param result result of delivering the message. Valid values are defined in
    * {@link DeliverStatusResult}
-   * @param messageRef the message reference or -1 of unavailable.
+   * @param messageRef the message reference
    */
-  public void acknowledgeSms(int messageRef, @DeliverStatusResult int result) {
+  public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
 
   }
 
   /**
    * This method will be triggered by the platform after
-   * {@link #onSmsStatusReportReceived(int, int, byte[])} has been called to provide the result to
-   * the IMS provider.
+   * {@link #onSmsStatusReportReceived(int, int, String, byte[])} has been called to provide the
+   * result to the IMS provider.
    *
+   * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
    * @param result result of delivering the message. Valid values are defined in
    * {@link StatusReportResult}
-   * @param messageRef the message reference or -1 of unavailable.
+   * @param messageRef the message reference
    */
-  public void acknowledgeSmsReport(int messageRef, @StatusReportResult int result) {
+  public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
 
   }
 
   /**
    * This method should be triggered by the IMS providers when there is an incoming message. The
    * platform will deliver the message to the messages database and notify the IMS provider of the
-   * result by calling {@link #acknowledgeSms(int, int)}.
+   * result by calling {@link #acknowledgeSms(int, int, int)}.
    *
    * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
    *
+   * @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 pdu PDUs representing the contents of the message.
    * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
    */
-  public final void onSmsReceived(String format, byte[] pdu) throws IllegalStateException {
+  public final void onSmsReceived(int token, String format, byte[] pdu)
+          throws IllegalStateException {
     synchronized (mLock) {
       if (mListener == null) {
         throw new IllegalStateException("Feature not ready.");
       }
       try {
-        mListener.onSmsReceived(format, pdu);
-        acknowledgeSms(-1, DELIVER_STATUS_OK);
+        mListener.onSmsReceived(token, format, pdu);
+        acknowledgeSms(token, 0, DELIVER_STATUS_OK);
       } catch (RemoteException e) {
         Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage());
-        acknowledgeSms(-1, DELIVER_STATUS_ERROR);
+        acknowledgeSms(token, 0, DELIVER_STATUS_ERROR);
       }
     }
   }
@@ -198,6 +207,7 @@
    *
    * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
    *
+   * @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 status result of sending the SMS. Valid values are defined in {@link SendStatusResult}
    * @param reason reason in case status is failure. Valid values are:
@@ -213,35 +223,37 @@
    * @throws RemoteException if the connection to the framework is not available. If this happens
    *  attempting to send the SMS should be aborted.
    */
-  public final void onSendSmsResult(int messageRef, @SendStatusResult int status, int reason)
-      throws IllegalStateException, RemoteException {
+  public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
+      int reason) throws IllegalStateException, RemoteException {
     synchronized (mLock) {
       if (mListener == null) {
         throw new IllegalStateException("Feature not ready.");
       }
-      mListener.onSendSmsResult(messageRef, status, reason);
+      mListener.onSendSmsResult(token, messageRef, status, reason);
     }
   }
 
   /**
    * Sets the status report of the sent message.
    *
+   * @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 pdu PDUs representing the content of the status report.
    * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
    */
-  public final void onSmsStatusReportReceived(int messageRef, String format, byte[] pdu) {
+  public final void onSmsStatusReportReceived(int token, int messageRef, String format,
+      byte[] pdu) {
     synchronized (mLock) {
       if (mListener == null) {
         throw new IllegalStateException("Feature not ready.");
       }
       try {
-        mListener.onSmsStatusReportReceived(messageRef, format, pdu);
+        mListener.onSmsStatusReportReceived(token, messageRef, format, pdu);
       } catch (RemoteException e) {
         Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage());
-        acknowledgeSmsReport(messageRef, STATUS_REPORT_STATUS_ERROR);
+        acknowledgeSmsReport(token, messageRef, STATUS_REPORT_STATUS_ERROR);
       }
     }
   }
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
index d976686..785113f 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
@@ -52,8 +52,9 @@
             IImsCapabilityCallback c);
     // SMS APIs
     void setSmsListener(IImsSmsListener l);
-    oneway void sendSms(int messageRef, String format, String smsc, boolean retry, in byte[] pdu);
-    oneway void acknowledgeSms(int messageRef, int result);
-    oneway void acknowledgeSmsReport(int messageRef, int result);
+    oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry,
+            in byte[] pdu);
+    oneway void acknowledgeSms(int token, int messageRef, int result);
+    oneway void acknowledgeSmsReport(int token, int messageRef, int result);
     String getSmsFormat();
 }
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl
index 8332bc0..43f5098 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl
@@ -24,4 +24,5 @@
  */
 oneway interface IImsMmTelListener {
     void onIncomingCall(IImsCallSession c);
+    void onVoiceMessageCountUpdate(int count);
 }
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl
index 8afb955..82a8525 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl
@@ -18,12 +18,12 @@
 
 import android.telephony.ims.internal.aidl.IImsMmTelFeature;
 import android.telephony.ims.internal.aidl.IImsRcsFeature;
-import android.telephony.ims.internal.aidl.IImsRegistration;
 import android.telephony.ims.internal.aidl.IImsConfig;
 import android.telephony.ims.internal.aidl.IImsServiceControllerListener;
 import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
+import com.android.ims.internal.IImsRegistration;
 
 /**
  * See ImsService and MmTelFeature for more information.
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
index 468629a..bf8d90b 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
@@ -21,7 +21,8 @@
  * {@hide}
  */
 interface IImsSmsListener {
-    void onSendSmsResult(in int messageRef, in int status, in int reason);
-    void onSmsStatusReportReceived(in int messageRef, in String format, in byte[] pdu);
-    void onSmsReceived(in String format, in byte[] pdu);
+    void onSendSmsResult(in int token, in int messageRef, in int status, in int reason);
+    void onSmsStatusReportReceived(in int token, in int messageRef, in String format,
+            in byte[] pdu);
+    void onSmsReceived(in int token, in String format, in byte[] pdu);
 }
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java
index 4d18873..5dbf077 100644
--- a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java
+++ b/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java
@@ -18,7 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.ims.internal.stub.ImsRegistrationImplBase;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.ArraySet;
 
 import java.util.ArrayList;
diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
index 2f350c8..8d888c2 100644
--- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
@@ -28,8 +28,8 @@
 import android.telephony.ims.internal.aidl.IImsCapabilityCallback;
 import android.telephony.ims.internal.aidl.IImsMmTelFeature;
 import android.telephony.ims.internal.aidl.IImsMmTelListener;
-import android.telephony.ims.internal.stub.ImsRegistrationImplBase;
 import android.telephony.ims.internal.aidl.IImsSmsListener;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.telephony.ims.stub.ImsEcbmImplBase;
 import android.telephony.ims.stub.ImsMultiEndpointImplBase;
 import android.telephony.ims.stub.ImsUtImplBase;
@@ -154,23 +154,24 @@
         }
 
         @Override
-        public void sendSms(int messageRef, String format, String smsc, boolean retry, byte[] pdu) {
+        public void sendSms(int token, int messageRef, String format, String smsc, boolean retry,
+                byte[] pdu) {
             synchronized (mLock) {
-                MmTelFeature.this.sendSms(messageRef, format, smsc, retry, pdu);
+                MmTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu);
             }
         }
 
         @Override
-        public void acknowledgeSms(int messageRef, int result) {
+        public void acknowledgeSms(int token, int messageRef, int result) {
             synchronized (mLock) {
-                MmTelFeature.this.acknowledgeSms(messageRef, result);
+                MmTelFeature.this.acknowledgeSms(token, messageRef, result);
             }
         }
 
         @Override
-        public void acknowledgeSmsReport(int messageRef, int result) {
+        public void acknowledgeSmsReport(int token, int messageRef, int result) {
             synchronized (mLock) {
-                MmTelFeature.this.acknowledgeSmsReport(messageRef, result);
+                MmTelFeature.this.acknowledgeSmsReport(token, messageRef, result);
             }
         }
 
@@ -261,6 +262,15 @@
         }
 
         /**
+         * Updates the Listener when the voice message count for IMS has changed.
+         * @param count an integer representing the new message count.
+         */
+        @Override
+        public void onVoiceMessageCountUpdate(int count) {
+
+        }
+
+        /**
          * Called when the IMS provider receives an incoming call.
          * @param c The {@link ImsCallSession} associated with the new call.
          */
@@ -447,16 +457,17 @@
         // Base Implementation - Should be overridden
     }
 
-    private void sendSms(int messageRef, String format, String smsc, boolean isRetry, byte[] pdu) {
-        getSmsImplementation().sendSms(messageRef, format, smsc, isRetry, pdu);
+    private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
+            byte[] pdu) {
+        getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu);
     }
 
-    private void acknowledgeSms(int messageRef, @DeliverStatusResult int result) {
-        getSmsImplementation().acknowledgeSms(messageRef, result);
+    private void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
+        getSmsImplementation().acknowledgeSms(token, messageRef, result);
     }
 
-    private void acknowledgeSmsReport(int messageRef, @StatusReportResult int result) {
-        getSmsImplementation().acknowledgeSmsReport(messageRef, result);
+    private void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
+        getSmsImplementation().acknowledgeSmsReport(token, messageRef, result);
     }
 
     private String getSmsFormat() {
diff --git a/telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
similarity index 83%
rename from telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java
rename to telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index 558b009..42af083 100644
--- a/telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -14,16 +14,19 @@
  * limitations under the License
  */
 
-package android.telephony.ims.internal.stub;
+package android.telephony.ims.stub;
 
 import android.annotation.IntDef;
+import android.net.Uri;
+import android.os.IBinder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.telephony.ims.internal.aidl.IImsRegistration;
-import android.telephony.ims.internal.aidl.IImsRegistrationCallback;
 import android.util.Log;
 
 import com.android.ims.ImsReasonInfo;
+import com.android.ims.internal.IImsRegistration;
+import com.android.ims.internal.IImsRegistrationCallback;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -62,23 +65,25 @@
 
     // Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current
     // state.
+    // The unknown state is set as the initialization state. This is so that we do not call back
+    // with NOT_REGISTERED in the case where the ImsService has not updated the registration state
+    // yet.
+    private static final int REGISTRATION_STATE_UNKNOWN = -1;
     private static final int REGISTRATION_STATE_NOT_REGISTERED = 0;
     private static final int REGISTRATION_STATE_REGISTERING = 1;
     private static final int REGISTRATION_STATE_REGISTERED = 2;
 
-
     /**
      * Callback class for receiving Registration callback events.
+     * @hide
      */
-    public static class Callback extends IImsRegistrationCallback.Stub {
-
+    public static class Callback {
         /**
          * Notifies the framework when the IMS Provider is connected to the IMS network.
          *
          * @param imsRadioTech the radio access technology. Valid values are defined in
          * {@link ImsRegistrationTech}.
          */
-        @Override
         public void onRegistered(@ImsRegistrationTech int imsRadioTech) {
         }
 
@@ -88,7 +93,6 @@
          * @param imsRadioTech the radio access technology. Valid values are defined in
          * {@link ImsRegistrationTech}.
          */
-        @Override
         public void onRegistering(@ImsRegistrationTech int imsRadioTech) {
         }
 
@@ -97,7 +101,6 @@
          *
          * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
          */
-        @Override
         public void onDeregistered(ImsReasonInfo info) {
         }
 
@@ -108,10 +111,19 @@
          * @param imsRadioTech The {@link ImsRegistrationTech} type that has failed
          * @param info A {@link ImsReasonInfo} that identifies the reason for failure.
          */
-        @Override
         public void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
                 ImsReasonInfo info) {
         }
+
+        /**
+         * Returns a list of subscriber {@link Uri}s associated with this IMS subscription when
+         * it changes.
+         * @param uris new array of subscriber {@link Uri}s that are associated with this IMS
+         *         subscription.
+         */
+        public void onSubscriberAssociatedUriChanged(Uri[] uris) {
+
+        }
     }
 
     private final IImsRegistration mBinder = new IImsRegistration.Stub() {
@@ -139,9 +151,9 @@
     private @ImsRegistrationTech
     int mConnectionType = REGISTRATION_TECH_NONE;
     // Locked on mLock
-    private int mRegistrationState = REGISTRATION_STATE_NOT_REGISTERED;
-    // Locked on mLock
-    private ImsReasonInfo mLastDisconnectCause;
+    private int mRegistrationState = REGISTRATION_STATE_UNKNOWN;
+    // Locked on mLock, create unspecified disconnect cause.
+    private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo();
 
     public final IImsRegistration getBinder() {
         return mBinder;
@@ -221,6 +233,17 @@
         });
     }
 
+    public final void onSubscriberAssociatedUriChanged(Uri[] uris) {
+        mCallbacks.broadcast((c) -> {
+            try {
+                c.onSubscriberAssociatedUriChanged(uris);
+            } catch (RemoteException e) {
+                Log.w(LOG_TAG, e + " " + "onSubscriberAssociatedUriChanged() - Skipping " +
+                        "callback.");
+            }
+        });
+    }
+
     private void updateToState(@ImsRegistrationTech int connType, int newState) {
         synchronized (mLock) {
             mConnectionType = connType;
@@ -241,7 +264,8 @@
         }
     }
 
-    private @ImsRegistrationTech int getConnectionType() {
+    @VisibleForTesting
+    public final @ImsRegistrationTech int getConnectionType() {
         synchronized (mLock) {
             return mConnectionType;
         }
@@ -271,6 +295,10 @@
                 c.onRegistered(getConnectionType());
                 break;
             }
+            case REGISTRATION_STATE_UNKNOWN: {
+                // Do not callback if the state has not been updated yet by the ImsService.
+                break;
+            }
         }
     }
 }
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index 489c208..693aaff 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -351,7 +351,7 @@
         mServiceType = in.readInt();
         mCallType = in.readInt();
         mCallExtras = in.readBundle();
-        mMediaProfile = in.readParcelable(null);
+        mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader());
     }
 
     public static final Creator<ImsCallProfile> CREATOR = new Creator<ImsCallProfile>() {
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl b/telephony/java/com/android/ims/internal/IImsRegistration.aidl
similarity index 82%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl
rename to telephony/java/com/android/ims/internal/IImsRegistration.aidl
index 687b7ca..6de264e 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl
+++ b/telephony/java/com/android/ims/internal/IImsRegistration.aidl
@@ -15,10 +15,9 @@
  */
 
 
-package android.telephony.ims.internal.aidl;
+package com.android.ims.internal;
 
-import android.telephony.ims.internal.aidl.IImsRegistrationCallback;
-import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
+import com.android.ims.internal.IImsRegistrationCallback;
 
 /**
  * See ImsRegistration for more information.
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl b/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl
similarity index 89%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl
rename to telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl
index a50575b..5f21167 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl
+++ b/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl
@@ -15,8 +15,9 @@
  */
 
 
-package android.telephony.ims.internal.aidl;
+package com.android.ims.internal;
 
+import android.net.Uri;
 import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
 
 import com.android.ims.ImsReasonInfo;
@@ -31,4 +32,5 @@
    void onRegistering(int imsRadioTech);
    void onDeregistered(in ImsReasonInfo info);
    void onTechnologyChangeFailed(int imsRadioTech, in ImsReasonInfo info);
+   void onSubscriberAssociatedUriChanged(in Uri[] uris);
 }
\ No newline at end of file
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
index 857089f..7ac25ac 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -18,6 +18,7 @@
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRegistration;
 import com.android.ims.internal.IImsRcsFeature;
 
 /**
@@ -29,4 +30,5 @@
     IImsMMTelFeature createMMTelFeature(int slotId, in IImsFeatureStatusCallback c);
     IImsRcsFeature createRcsFeature(int slotId, in IImsFeatureStatusCallback c);
     void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c);
+    IImsRegistration getRegistration(int slotId);
 }
diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
index ac16139..8e3f4c0 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -46,5 +46,6 @@
     void onVoiceActivationStateChanged(int activationState);
     void onDataActivationStateChanged(int activationState);
     void onCarrierNetworkChange(in boolean active);
+    void onUserMobileDataStateChanged(in boolean enabled);
 }
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 416146f..fba82ee 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -40,6 +40,7 @@
 import android.telephony.VisualVoicemailSmsFilterSettings;
 import com.android.ims.internal.IImsMMTelFeature;
 import com.android.ims.internal.IImsRcsFeature;
+import com.android.ims.internal.IImsRegistration;
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.OperatorInfo;
@@ -808,6 +809,11 @@
     IImsRcsFeature getRcsFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
 
     /**
+    * Returns the IImsRegistration associated with the slot and feature specified.
+    */
+    IImsRegistration getImsRegistration(int slotId, int feature);
+
+    /**
      * Set the network selection mode to automatic.
      *
      * @param subId the id of the subscription to update.
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 75d8f3f..188167c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -69,4 +69,5 @@
             int activationState, int activationType);
     void notifySubscriptionInfoChanged();
     void notifyCarrierNetworkChange(in boolean active);
+    void notifyUserMobileDataStateChangedForPhoneId(in int phoneId, in int subId, in boolean state);
 }
diff --git a/telephony/java/android/telephony/data/InterfaceAddress.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
similarity index 65%
copy from telephony/java/android/telephony/data/InterfaceAddress.aidl
copy to telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
index d750363..2846a1a 100644
--- a/telephony/java/android/telephony/data/InterfaceAddress.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 The Android Open Source Project
+ * 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.
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-/** @hide */
-package android.telephony.data;
+package com.android.internal.telephony.euicc;
 
-parcelable InterfaceAddress;
+import com.android.internal.telephony.euicc.IGetAllProfilesCallback;
+
+/** @hide */
+interface IEuiccCardController {
+    oneway void getAllProfiles(String callingPackage, in IGetAllProfilesCallback callback);
+}
diff --git a/telephony/java/android/telephony/data/InterfaceAddress.aidl b/telephony/java/com/android/internal/telephony/euicc/IGetAllProfilesCallback.aidl
similarity index 68%
copy from telephony/java/android/telephony/data/InterfaceAddress.aidl
copy to telephony/java/com/android/internal/telephony/euicc/IGetAllProfilesCallback.aidl
index d750363..97b0768 100644
--- a/telephony/java/android/telephony/data/InterfaceAddress.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IGetAllProfilesCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 The Android Open Source Project
+ * 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.
@@ -13,8 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.internal.telephony.euicc;
+
+import android.service.euicc.EuiccProfileInfo;
 
 /** @hide */
-package android.telephony.data;
-
-parcelable InterfaceAddress;
+oneway interface IGetAllProfilesCallback {
+    void onComplete(int resultCode, in EuiccProfileInfo[] profiles);
+}
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 0088962..ccf57b0 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -49,7 +49,8 @@
 
 // Build the repackaged.android.test.base library
 // ==============================================
-// This contains repackaged versions of the classes from legacy-test.
+// This contains repackaged versions of the classes from
+// android.test.base.
 java_library_static {
     name: "repackaged.android.test.base",
 
@@ -97,7 +98,7 @@
     ],
 
     static_libs: [
-        "android.test.runner",
+        "android.test.runner-minus-junit",
         "android.test.mock",
     ],
 
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 8eddec4..b1ae40e 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -24,7 +24,6 @@
     no_framework_libs: true,
     libs: [
         "framework",
-        "legacy-test",
     ],
 }
 
diff --git a/test-mock/src/android/test/mock/MockPackageManager.java b/test-mock/src/android/test/mock/MockPackageManager.java
index 7207ebc..13e3693 100644
--- a/test-mock/src/android/test/mock/MockPackageManager.java
+++ b/test-mock/src/android/test/mock/MockPackageManager.java
@@ -1174,4 +1174,20 @@
     public ArtManager getArtManager() {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void setHarmfulAppWarning(String packageName, CharSequence warning) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public CharSequence getHarmfulAppWarning(String packageName) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 104ae82..dfaeed5 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -24,11 +24,28 @@
     no_framework_libs: true,
     libs: [
         "framework",
-        "legacy-test",
+        "android.test.base",
         "android.test.mock",
     ],
 }
 
+// Build the android.test.runner-minus-junit library
+// =================================================
+// This is provided solely for use by the legacy-android-test module.
+java_library {
+    name: "android.test.runner-minus-junit",
+
+    srcs: ["src/android/**/*.java"],
+
+    no_framework_libs: true,
+    libs: [
+        "framework",
+        "android.test.base",
+        "android.test.mock",
+        "junit",
+    ],
+}
+
 // Build the repackaged.android.test.runner library
 // ================================================
 java_library_static {
diff --git a/tests/ActivityManagerPerfTests/README.txt b/tests/ActivityManagerPerfTests/README.txt
new file mode 100644
index 0000000..77e0e90
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/README.txt
@@ -0,0 +1,34 @@
+ActivityManagerPerfTests
+
+Performance tests for various ActivityManager components, e.g. Services, Broadcasts
+
+Command to run tests (not working yet, atest seems buggy)
+* atest .../frameworks/base/tests/ActivityManagerPerfTests
+* m ActivityManagerPerfTests ActivityManagerPerfTestsTestApp && \
+  adb install $OUT/data/app/ActivityManagerPerfTests/ActivityManagerPerfTests.apk && \
+  adb install $OUT/data/app/ActivityManagerPerfTestsTestApp/ActivityManagerPerfTestsTestApp.apk && \
+  adb shell am instrument -w \
+  com.android.frameworks.perftests.amtests/android.support.test.runner.AndroidJUnitRunner
+
+Overview
+* The numbers we are trying to measure are end-to-end numbers
+  * For example, the time it takes from sending an Intent to start a Service
+    to the time the Service runs its callbacks
+* System.nanoTime() is monotonic and consistent between processes, so we use that for measuring time
+* To make sure the test app is running, we start an Activity
+* If the test app is involved, it will measure the time and send it back to the instrumentation test
+  * The time is sent back through a Binder interface in the Intent
+  * Each sent time is tagged with an id since there can be multiple events that send back a time
+    * For example, one is sent when the Activity is started, and another could be sent when a
+      Broadcast is received
+
+Structure
+* tests
+  * Instrumentation test which runs the various performance tests and reports the results
+
+* test-app
+  * Target package which contains the Services, BroadcastReceivers, etc. to test against
+  * Sends the time it measures back to the test package
+
+* utils
+  * Utilities that both the instrumentation test and test app can use
diff --git a/tests/ActivityManagerPerfTests/test-app/Android.mk b/tests/ActivityManagerPerfTests/test-app/Android.mk
new file mode 100644
index 0000000..b0a5db7
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/test-app/Android.mk
@@ -0,0 +1,28 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ActivityManagerPerfTestsUtils
+
+LOCAL_PACKAGE_NAME := ActivityManagerPerfTestsTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/tests/ActivityManagerPerfTests/test-app/AndroidManifest.xml b/tests/ActivityManagerPerfTests/test-app/AndroidManifest.xml
new file mode 100644
index 0000000..7145110
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/test-app/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.frameworks.perftests.amteststestapp">
+    <application android:name=".TestApplication">
+        <activity android:name=".TestActivity" android:exported="true"/>
+        <receiver
+            android:name=".TestBroadcastReceiver"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.frameworks.perftests.ACTION_BROADCAST_MANIFEST_RECEIVE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java
new file mode 100644
index 0000000..7ea9ba3
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+package com.android.frameworks.perftests.amteststestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.perftests.am.util.Constants;
+import com.android.frameworks.perftests.am.util.Utils;
+
+public class TestActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Utils.sendTime(getIntent(), Constants.TYPE_ACTIVITY_CREATED);
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestApplication.java b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestApplication.java
new file mode 100644
index 0000000..e26ffcd
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestApplication.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+package com.android.frameworks.perftests.amteststestapp;
+
+import android.app.Application;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import com.android.frameworks.perftests.am.util.Constants;
+import com.android.frameworks.perftests.am.util.Utils;
+
+public class TestApplication extends Application {
+    private static final String TAG = TestApplication.class.getSimpleName();
+
+    @Override
+    public void onCreate() {
+        createRegisteredReceiver();
+
+        super.onCreate();
+    }
+
+    // Create registered BroadcastReceiver
+    private void createRegisteredReceiver() {
+        BroadcastReceiver registered = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Log.i(TAG, "RegisteredReceiver.onReceive");
+                Utils.sendTime(intent, Constants.TYPE_BROADCAST_RECEIVE);
+            }
+        };
+        IntentFilter intentFilter = new IntentFilter(Constants.ACTION_BROADCAST_REGISTERED_RECEIVE);
+        registerReceiver(registered, intentFilter);
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestBroadcastReceiver.java b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestBroadcastReceiver.java
new file mode 100644
index 0000000..336bf9d
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestBroadcastReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+package com.android.frameworks.perftests.amteststestapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.frameworks.perftests.am.util.Constants;
+import com.android.frameworks.perftests.am.util.Utils;
+
+public class TestBroadcastReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Utils.sendTime(intent, Constants.TYPE_BROADCAST_RECEIVE);
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/Android.mk b/tests/ActivityManagerPerfTests/tests/Android.mk
new file mode 100644
index 0000000..daf603d
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/Android.mk
@@ -0,0 +1,33 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    apct-perftests-utils \
+    ActivityManagerPerfTestsUtils
+
+LOCAL_PACKAGE_NAME := ActivityManagerPerfTests
+
+# For android.permission.FORCE_STOP_PACKAGES permission
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/tests/ActivityManagerPerfTests/tests/AndroidManifest.xml b/tests/ActivityManagerPerfTests/tests/AndroidManifest.xml
new file mode 100644
index 0000000..4e194c6
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.perftests.amtests">
+    <uses-permission android:name="android.permission.DUMP" />
+    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.frameworks.perftests.amtests"/>
+</manifest>
diff --git a/tests/ActivityManagerPerfTests/tests/AndroidTest.xml b/tests/ActivityManagerPerfTests/tests/AndroidTest.xml
new file mode 100644
index 0000000..ffb5404
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs ActivityManager Performance Tests">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="ActivityManagerPerfTests.apk"/>
+        <option name="test-file-name" value="ActivityManagerPerfTestsTestApp.apk"/>
+        <option name="cleanup-apks" value="true"/>
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-tag" value="ActivityManagerPerfTests"/>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.frameworks.perftests.amtests"/>
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java
new file mode 100644
index 0000000..661abe9
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+package com.android.frameworks.perftests.am.tests;
+
+import android.content.Context;
+import android.content.Intent;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.frameworks.perftests.am.util.TargetPackageUtils;
+import com.android.frameworks.perftests.am.util.TimeReceiver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+
+import java.util.function.LongSupplier;
+
+public class BasePerfTest {
+    private static final String TAG = BasePerfTest.class.getSimpleName();
+
+    private TimeReceiver mTimeReceiver;
+
+    @Rule
+    public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
+
+    protected Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mTimeReceiver = new TimeReceiver();
+    }
+
+    @After
+    public void tearDown() {
+        TargetPackageUtils.killTargetPackage(mContext);
+    }
+
+    protected Intent createIntent(String action) {
+        final Intent intent = new Intent(action);
+        intent.addFlags(
+                Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+        intent.putExtras(mTimeReceiver.createReceiveTimeExtraBinder());
+        return intent;
+    }
+
+    private void setUpIteration() {
+        mTimeReceiver.clear();
+        TargetPackageUtils.killTargetPackage(mContext);
+    }
+
+    protected void startTargetPackage() {
+        TargetPackageUtils.startTargetPackage(mContext, mTimeReceiver);
+    }
+
+    protected long getReceivedTimeNs(String type) {
+        return mTimeReceiver.getReceivedTimeNs(type);
+    }
+
+    protected void runPerfFunction(LongSupplier func) {
+        final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
+        long elapsedTimeNs = 0;
+        while (benchmarkState.keepRunning(elapsedTimeNs)) {
+            setUpIteration();
+            elapsedTimeNs = func.getAsLong();
+        }
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java
new file mode 100644
index 0000000..795f498
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+package com.android.frameworks.perftests.am.tests;
+
+import android.content.Intent;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.perftests.am.util.Constants;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class BroadcastPerfTest extends BasePerfTest {
+    @Test
+    public void manifestBroadcastRunning() {
+        runPerfFunction(() -> {
+            startTargetPackage();
+
+            final Intent intent = createIntent(Constants.ACTION_BROADCAST_MANIFEST_RECEIVE);
+
+            final long startTime = System.nanoTime();
+
+            mContext.sendBroadcast(intent);
+
+            final long endTime = getReceivedTimeNs(Constants.TYPE_BROADCAST_RECEIVE);
+
+            return endTime - startTime;
+        });
+    }
+
+    @Test
+    public void manifestBroadcastNotRunning() {
+        runPerfFunction(() -> {
+            final Intent intent = createIntent(Constants.ACTION_BROADCAST_MANIFEST_RECEIVE);
+
+            final long startTime = System.nanoTime();
+
+            mContext.sendBroadcast(intent);
+
+            final long endTime = getReceivedTimeNs(Constants.TYPE_BROADCAST_RECEIVE);
+
+            return endTime - startTime;
+        });
+    }
+
+    @Test
+    public void registeredBroadcast() {
+        runPerfFunction(() -> {
+            startTargetPackage();
+
+            final Intent intent = createIntent(Constants.ACTION_BROADCAST_REGISTERED_RECEIVE);
+
+            final long startTime = System.nanoTime();
+
+            mContext.sendBroadcast(intent);
+
+            final long endTime = getReceivedTimeNs(Constants.TYPE_BROADCAST_RECEIVE);
+
+            return endTime - startTime;
+        });
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
new file mode 100644
index 0000000..c867141
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+package com.android.frameworks.perftests.am.util;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.SystemClock;
+
+public class TargetPackageUtils {
+    private static final String TAG = TargetPackageUtils.class.getSimpleName();
+
+    public static final String PACKAGE_NAME = "com.android.frameworks.perftests.amteststestapp";
+    public static final String ACTIVITY_NAME = PACKAGE_NAME + ".TestActivity";
+
+    private static final long WAIT_TIME_MS = 100L;
+
+    // Cache for test app's uid, so we only have to query it once.
+    private static int sTestAppUid = -1;
+
+    /**
+     * Kills the test package synchronously.
+     */
+    public static void killTargetPackage(Context context) {
+        ActivityManager activityManager = context.getSystemService(ActivityManager.class);
+        activityManager.forceStopPackage(PACKAGE_NAME);
+        while (targetPackageIsRunning(context)) {
+            sleep();
+        }
+
+        Utils.drainBroadcastQueue();
+    }
+
+    /**
+     * Starts the test package synchronously. It does so by starting an Activity.
+     */
+    public static void startTargetPackage(Context context, TimeReceiver timeReceiver) {
+        // "am start-activity -W PACKAGE_NAME/ACTIVITY_CLASS_NAME" still requires a sleep even
+        // though it should be synchronous, so just use Intent instead
+        final Intent intent = new Intent();
+        intent.putExtras(timeReceiver.createReceiveTimeExtraBinder());
+        intent.setClassName(PACKAGE_NAME, ACTIVITY_NAME);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+
+        while (!targetPackageIsRunning(context)) {
+            sleep();
+        }
+        // make sure Application has run
+        timeReceiver.getReceivedTimeNs(Constants.TYPE_ACTIVITY_CREATED);
+        Utils.drainBroadcastQueue();
+    }
+
+    private static boolean targetPackageIsRunning(Context context) {
+        final int uid = getTestAppUid(context);
+        final String result = Utils.runShellCommand(
+                String.format("cmd activity get-uid-state %d", uid));
+        return !result.contains("(NONEXISTENT)");
+    }
+
+    private static void sleep() {
+        SystemClock.sleep(WAIT_TIME_MS);
+    }
+
+    private static int getTestAppUid(Context context) {
+        if (sTestAppUid == -1) {
+            final PackageManager pm = context.getPackageManager();
+            try {
+                sTestAppUid = pm.getPackageUid(PACKAGE_NAME, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return sTestAppUid;
+    }
+
+}
+
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TimeReceiver.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TimeReceiver.java
new file mode 100644
index 0000000..9cf6ee7
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TimeReceiver.java
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+package com.android.frameworks.perftests.am.util;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * TimeReceiver will listen for any messages containing a timestamp by starting a BroadcastReceiver
+ * which listens for Intents with the SendTime.ACTION_SEND_TIME action.
+ */
+public class TimeReceiver {
+    private static final String TAG = TimeReceiver.class.getSimpleName();
+    private static final long DEFAULT_RECEIVE_TIME_TIMEOUT_MILLIS = 10000L;
+
+    private BlockingQueue<ReceivedMessage> mQueue = new LinkedBlockingQueue<>();
+
+    private static class ReceivedMessage {
+        private final String mReceivedMessageType;
+        private final long mReceivedTimeNs;
+
+        public ReceivedMessage(String receivedMessageType, long receivedTimeNs) {
+            mReceivedMessageType = receivedMessageType;
+            mReceivedTimeNs = receivedTimeNs;
+        }
+    }
+
+    public Bundle createReceiveTimeExtraBinder() {
+        Bundle extras = new Bundle();
+        extras.putBinder(Constants.EXTRA_RECEIVER_CALLBACK, new ITimeReceiverCallback.Stub() {
+            @Override
+            public void sendTime(String type, long timeNs) throws RemoteException {
+                if (type == null) {
+                    throw new RuntimeException("receivedType is null");
+                }
+                if (timeNs < 0) {
+                    throw new RuntimeException(
+                            "receivedTime is negative/non-existant: " + timeNs);
+                }
+                Log.i(TAG, type + " " + timeNs);
+                mQueue.add(new ReceivedMessage(type, timeNs));
+            }
+        });
+        return extras;
+    }
+
+    public long getReceivedTimeNs(String type) {
+        return getReceivedTimeNs(type, DEFAULT_RECEIVE_TIME_TIMEOUT_MILLIS);
+    }
+
+    /**
+     * Returns a received timestamp with the given type tag. Will throw away any messages with a
+     * different type tag. If it times out, a RuntimeException is thrown.
+     */
+    public long getReceivedTimeNs(String type, long timeoutMs) {
+        ReceivedMessage message;
+        long endTimeNs = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMs);
+        do {
+            long curTimeNs = System.nanoTime();
+            if (curTimeNs > endTimeNs) {
+                throw new RuntimeException("Timed out when listening for a time: " + type);
+            }
+            try {
+                Log.i(TAG, "waiting for message " + type);
+                message = mQueue.poll(endTimeNs - curTimeNs, TimeUnit.NANOSECONDS);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+            if (message == null) {
+                throw new RuntimeException("Timed out when listening for a time: " + type);
+            }
+            Log.i(TAG, "got message " + message.mReceivedMessageType);
+            if (!type.equals(message.mReceivedMessageType)) {
+                Log.i(TAG, String.format("Expected type \"%s\", got \"%s\" (%d), skipping", type,
+                        message.mReceivedMessageType, message.mReceivedTimeNs));
+            }
+        } while (!type.equals(message.mReceivedMessageType));
+        return message.mReceivedTimeNs;
+    }
+
+    /**
+     * Clears the message queue.
+     */
+    public void clear() {
+        mQueue.clear();
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/utils/Android.mk b/tests/ActivityManagerPerfTests/utils/Android.mk
new file mode 100644
index 0000000..7276e37
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/utils/Android.mk
@@ -0,0 +1,31 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.aidl
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    junit \
+    ub-uiautomator
+
+LOCAL_MODULE := ActivityManagerPerfTestsUtils
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
new file mode 100644
index 0000000..6528028
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package com.android.frameworks.perftests.am.util;
+
+public class Constants {
+    public static final String TYPE_ACTIVITY_CREATED = "activity_create";
+    public static final String TYPE_BROADCAST_RECEIVE = "broadcast_receive";
+
+    public static final String ACTION_BROADCAST_MANIFEST_RECEIVE =
+            "com.android.frameworks.perftests.ACTION_BROADCAST_MANIFEST_RECEIVE";
+    public static final String ACTION_BROADCAST_REGISTERED_RECEIVE =
+            "com.android.frameworks.perftests.ACTION_BROADCAST_REGISTERED_RECEIVE";
+
+    public static final String EXTRA_RECEIVER_CALLBACK = "receiver_callback_binder";
+}
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.aidl
similarity index 74%
copy from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
copy to tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.aidl
index bd76051..b43d49a 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-package android.security.recoverablekeystore;
+package com.android.frameworks.perftests.am.util;
 
-/* @hide */
-parcelable KeyStoreRecoveryData;
+interface ITimeReceiverCallback {
+    void sendTime(String type, long timeNs);
+}
\ No newline at end of file
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java
new file mode 100644
index 0000000..493d8cd
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package com.android.frameworks.perftests.am.util;
+
+import android.content.Intent;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import java.io.IOException;
+
+public class Utils {
+    private static final String TAG = "AmPerfTestsUtils";
+
+    public static void drainBroadcastQueue() {
+        runShellCommand("am wait-for-broadcast-idle");
+    }
+
+    /**
+     * Runs the command and returns the stdout.
+     */
+    public static String runShellCommand(String cmd) {
+        try {
+            return UiDevice.getInstance(
+                    InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Sends the current time in a message with the given type so TimeReceiver can receive it.
+     */
+    public static void sendTime(Intent intent, String type) {
+        final long time = System.nanoTime();
+        final ITimeReceiverCallback sendTimeBinder = ITimeReceiverCallback.Stub.asInterface(
+                intent.getExtras().getBinder(Constants.EXTRA_RECEIVER_CALLBACK));
+        try {
+            sendTimeBinder.sendTime(type, time);
+        } catch (RemoteException e) {
+            Log.e(TAG, e.getMessage());
+        }
+    }
+}
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index 473dc538..9aad413 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -67,7 +67,7 @@
             assertEquals(msg, t.expectedType, got);
 
             if (got != MacAddress.TYPE_UNKNOWN) {
-                assertEquals(got, MacAddress.fromBytes(t.addr).addressType());
+                assertEquals(got, MacAddress.fromBytes(t.addr).getAddressType());
             }
         }
     }
@@ -191,7 +191,7 @@
 
             assertTrue(stringRepr + " expected to be a locally assigned address",
                     mac.isLocallyAssigned());
-            assertEquals(MacAddress.TYPE_UNICAST, mac.addressType());
+            assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType());
             assertTrue(stringRepr + " expected to begin with " + expectedLocalOui,
                     stringRepr.startsWith(expectedLocalOui));
         }
diff --git a/tests/net/java/android/net/NetworkTest.java b/tests/net/java/android/net/NetworkTest.java
index bacf986..94d01e9 100644
--- a/tests/net/java/android/net/NetworkTest.java
+++ b/tests/net/java/android/net/NetworkTest.java
@@ -147,9 +147,9 @@
 
         // Adjust as necessary to test an implementation's specific constants.
         // When running with runtest, "adb logcat -s TestRunner" can be useful.
-        assertEquals(4311403230L, one.getNetworkHandle());
-        assertEquals(8606370526L, two.getNetworkHandle());
-        assertEquals(12901337822L, three.getNetworkHandle());
+        assertEquals(7700664333L, one.getNetworkHandle());
+        assertEquals(11995631629L, two.getNetworkHandle());
+        assertEquals(16290598925L, three.getNetworkHandle());
     }
 
     private static <T> void assertNotEqual(T t1, T t2) {
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 2b0349c..b8e37f3 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -1392,39 +1392,75 @@
             return null;
         }
 
-        void expectAvailableCallbacks(
-                MockNetworkAgent agent, boolean expectSuspended, int timeoutMs) {
+        // Expects onAvailable and the callbacks that follow it. These are:
+        // - onSuspended, iff the network was suspended when the callbacks fire.
+        // - onCapabilitiesChanged.
+        // - onLinkPropertiesChanged.
+        //
+        // @param agent the network to expect the callbacks on.
+        // @param expectSuspended whether to expect a SUSPENDED callback.
+        // @param expectValidated the expected value of the VALIDATED capability in the
+        //        onCapabilitiesChanged callback.
+        // @param timeoutMs how long to wait for the callbacks.
+        void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended,
+                boolean expectValidated, int timeoutMs) {
             expectCallback(CallbackState.AVAILABLE, agent, timeoutMs);
             if (expectSuspended) {
                 expectCallback(CallbackState.SUSPENDED, agent, timeoutMs);
             }
-            expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs);
+            if (expectValidated) {
+                expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
+            } else {
+                expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, agent);
+            }
             expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs);
         }
 
-        void expectAvailableCallbacks(MockNetworkAgent agent) {
-            expectAvailableCallbacks(agent, false, TIMEOUT_MS);
+        // Expects the available callbacks (validated), plus onSuspended.
+        void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent, boolean expectValidated) {
+            expectAvailableCallbacks(agent, true, expectValidated, TIMEOUT_MS);
         }
 
-        void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent) {
-            expectAvailableCallbacks(agent, true, TIMEOUT_MS);
+        void expectAvailableCallbacksValidated(MockNetworkAgent agent) {
+            expectAvailableCallbacks(agent, false, true, TIMEOUT_MS);
         }
 
-        void expectAvailableAndValidatedCallbacks(MockNetworkAgent agent) {
-            expectAvailableCallbacks(agent, false, TIMEOUT_MS);
+        void expectAvailableCallbacksUnvalidated(MockNetworkAgent agent) {
+            expectAvailableCallbacks(agent, false, false, TIMEOUT_MS);
+        }
+
+        // Expects the available callbacks (where the onCapabilitiesChanged must contain the
+        // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the
+        // one we just sent.
+        // TODO: this is likely a bug. Fix it and remove this method.
+        void expectAvailableDoubleValidatedCallbacks(MockNetworkAgent agent) {
+            expectCallback(CallbackState.AVAILABLE, agent, TIMEOUT_MS);
+            NetworkCapabilities nc1 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
+            expectCallback(CallbackState.LINK_PROPERTIES, agent, TIMEOUT_MS);
+            NetworkCapabilities nc2 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
+            assertEquals(nc1, nc2);
+        }
+
+        // Expects the available callbacks where the onCapabilitiesChanged must not have validated,
+        // then expects another onCapabilitiesChanged that has the validated bit set. This is used
+        // when a network connects and satisfies a callback, and then immediately validates.
+        void expectAvailableThenValidatedCallbacks(MockNetworkAgent agent) {
+            expectAvailableCallbacksUnvalidated(agent);
             expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
         }
 
-        void expectCapabilitiesWith(int capability, MockNetworkAgent agent) {
+        NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent) {
             CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
             NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
             assertTrue(nc.hasCapability(capability));
+            return nc;
         }
 
-        void expectCapabilitiesWithout(int capability, MockNetworkAgent agent) {
+        NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent) {
             CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
             NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
             assertFalse(nc.hasCapability(capability));
+            return nc;
         }
 
         void assertNoCallback() {
@@ -1461,8 +1497,8 @@
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);
-        genericNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
-        cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         waitFor(cv);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1476,8 +1512,8 @@
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
-        wifiNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         waitFor(cv);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1500,8 +1536,8 @@
         // Test validated networks
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        genericNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
-        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        genericNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
@@ -1513,10 +1549,10 @@
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        wifiNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
         cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1552,32 +1588,32 @@
         mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
 
         mCellNetworkAgent.connect(true);
-        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.connect(true);
         // We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request.
         // We then get LOSING when wifi validates and cell is outscored.
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mEthernetNetworkAgent.connect(true);
-        callback.expectAvailableCallbacks(mEthernetNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
         // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
         assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mEthernetNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
 
         for (int i = 0; i < 4; i++) {
             MockNetworkAgent oldNetwork, newNetwork;
@@ -1594,7 +1630,7 @@
             callback.expectCallback(CallbackState.LOSING, oldNetwork);
             // TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no
             // longer lingering?
-            defaultCallback.expectAvailableCallbacks(newNetwork);
+            defaultCallback.expectAvailableCallbacksValidated(newNetwork);
             assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork());
         }
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1614,7 +1650,7 @@
         // Disconnect our test networks.
         mWiFiNetworkAgent.disconnect();
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
 
@@ -1630,22 +1666,22 @@
 
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);   // Score: 10
-        callback.expectAvailableCallbacks(mCellNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi with a score of 20.
         // Cell stays up because it would satisfy the default request if it validated.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);   // Score: 20
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi with a score of 70.
@@ -1653,33 +1689,33 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.adjustScore(50);
         mWiFiNetworkAgent.connect(false);   // Score: 70
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Tear down wifi.
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but
         // it's arguably correct to linger it, since it was the default network before it validated.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
@@ -1687,12 +1723,12 @@
         // If a network is lingering, and we add and remove a request from it, resume lingering.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
@@ -1711,7 +1747,7 @@
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
 
         // Cell is now the default network. Pin it with a cell-specific request.
         noopCallback = new NetworkCallback();  // Can't reuse NetworkCallbacks. http://b/20701525
@@ -1720,8 +1756,8 @@
         // Now connect wifi, and expect it to become the default network.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         // The default request is lingering on cell, but nothing happens to cell, and we send no
         // callbacks for it, because it's kept up by cellRequest.
         callback.assertNoCallback();
@@ -1737,14 +1773,14 @@
         // Register a TRACK_DEFAULT request and check that it does not affect lingering.
         TestNetworkCallback trackDefaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(trackDefaultCallback);
-        trackDefaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        trackDefaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
         mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
         mEthernetNetworkAgent.connect(true);
-        callback.expectAvailableCallbacks(mEthernetNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
         callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
-        trackDefaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+        trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
 
         // Let linger run its course.
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
@@ -1771,13 +1807,13 @@
         // Bring up validated cell.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
 
         // Bring up unvalidated wifi with explicitlySelected=true.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.explicitlySelected(false);
         mWiFiNetworkAgent.connect(false);
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
 
         // Cell Remains the default.
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1800,7 +1836,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.explicitlySelected(false);
         mWiFiNetworkAgent.connect(false);
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
 
         // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the
         // network to disconnect.
@@ -1811,7 +1847,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.explicitlySelected(false);
         mWiFiNetworkAgent.connect(true);
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1820,7 +1856,7 @@
         // TODO: fix this.
         mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
         mEthernetNetworkAgent.connect(true);
-        callback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+        callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
         assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         callback.assertNoCallback();
 
@@ -1993,7 +2029,7 @@
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mCellNetworkAgent.connectWithoutInternet();
-        networkCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         verifyActiveNetwork(TRANSPORT_WIFI);
 
         // Test releasing NetworkRequest disconnects cellular with MMS
@@ -2022,7 +2058,7 @@
         MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mmsNetworkAgent.connectWithoutInternet();
-        networkCallback.expectAvailableCallbacks(mmsNetworkAgent);
+        networkCallback.expectAvailableCallbacksUnvalidated(mmsNetworkAgent);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
 
         // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
@@ -2049,7 +2085,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         String firstRedirectUrl = "http://example.com/firstPath";
         mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
-        captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
 
         // Take down network.
@@ -2062,7 +2098,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         String secondRedirectUrl = "http://example.com/secondPath";
         mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
-        captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl);
 
         // Make captive portal disappear then revalidate.
@@ -2072,9 +2108,7 @@
         captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
-        validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
-        // TODO: Investigate only sending available callbacks.
-        validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
 
         // Break network connectivity.
         // Expect NET_CAPABILITY_VALIDATED onLost callback.
@@ -2098,7 +2132,7 @@
         // Bring up wifi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        validatedCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Check that calling startCaptivePortalApp does nothing.
@@ -2109,7 +2143,7 @@
         // Turn into a captive portal.
         mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302;
         mCm.reportNetworkConnectivity(wifiNetwork, false);
-        captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // Check that startCaptivePortalApp sends the expected intent.
@@ -2122,7 +2156,7 @@
         mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
         CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
         c.reportCaptivePortalDismissed();
-        validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
         captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         mCm.unregisterNetworkCallback(validatedCallback);
@@ -2165,7 +2199,7 @@
         mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
 
         // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
-        validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
         // But there should be no CaptivePortal callback.
         captivePortalCallback.assertNoCallback();
     }
@@ -2203,14 +2237,14 @@
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        cEmpty1.expectAvailableCallbacks(mWiFiNetworkAgent);
-        cEmpty2.expectAvailableCallbacks(mWiFiNetworkAgent);
-        cEmpty3.expectAvailableCallbacks(mWiFiNetworkAgent);
-        cEmpty4.expectAvailableCallbacks(mWiFiNetworkAgent);
+        cEmpty1.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        cEmpty2.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        cEmpty3.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        cEmpty4.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertNoCallbacks(cFoo, cBar);
 
         mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("foo"));
-        cFoo.expectAvailableCallbacks(mWiFiNetworkAgent);
+        cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         for (TestNetworkCallback c: emptyCallbacks) {
             c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
         }
@@ -2219,7 +2253,7 @@
 
         mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("bar"));
         cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        cBar.expectAvailableCallbacks(mWiFiNetworkAgent);
+        cBar.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         for (TestNetworkCallback c: emptyCallbacks) {
             c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
         }
@@ -2348,14 +2382,14 @@
         // Bring up cell and expect CALLBACK_AVAILABLE.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
-        defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
 
         // Bring up wifi and expect CALLBACK_AVAILABLE.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         cellNetworkCallback.assertNoCallback();
-        defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
 
         // Bring down cell. Expect no default network callback, since it wasn't the default.
         mCellNetworkAgent.disconnect();
@@ -2365,7 +2399,7 @@
         // Bring up cell. Expect no default network callback, since it won't be the default.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         defaultNetworkCallback.assertNoCallback();
 
         // Bring down wifi. Expect the default network callback to notified of LOST wifi
@@ -2373,7 +2407,7 @@
         mWiFiNetworkAgent.disconnect();
         cellNetworkCallback.assertNoCallback();
         defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
         defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
@@ -2394,7 +2428,7 @@
         // We should get onAvailable(), onCapabilitiesChanged(), and
         // onLinkPropertiesChanged() in rapid succession. Additionally, we
         // should get onCapabilitiesChanged() when the mobile network validates.
-        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
 
         // Update LinkProperties.
@@ -2415,7 +2449,7 @@
         mCm.registerDefaultNetworkCallback(dfltNetworkCallback);
         // We should get onAvailable(), onCapabilitiesChanged(), onLinkPropertiesChanged(),
         // as well as onNetworkSuspended() in rapid succession.
-        dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent);
+        dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent, true);
         dfltNetworkCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(dfltNetworkCallback);
@@ -2455,18 +2489,18 @@
 
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
-        fgCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        fgCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
 
         // When wifi connects, cell lingers.
-        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        fgCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        fgCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
@@ -2490,8 +2524,8 @@
         // is currently delivered before the onAvailable() callbacks.
         // TODO: Fix this.
         cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
-        cellCallback.expectAvailableCallbacks(mCellNetworkAgent);
-        fgCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        cellCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         // Expect a network capabilities update with FOREGROUND, because the most recent
         // request causes its state to change.
         callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
@@ -2511,7 +2545,7 @@
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        fgCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
 
         mCm.unregisterNetworkCallback(callback);
@@ -2651,7 +2685,7 @@
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         testFactory.expectAddRequests(2);  // Because the cell request changes score twice.
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         testFactory.waitForNetworkRequests(2);
         assertFalse(testFactory.getMyStartRequested());  // Because the cell network outscores us.
 
@@ -2742,16 +2776,15 @@
         // Bring up validated cell.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         Network cellNetwork = mCellNetworkAgent.getNetwork();
 
         // Bring up validated wifi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
-        validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
-        validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi.
@@ -2772,18 +2805,18 @@
         // that we switch back to cell.
         tracker.configRestrictsAvoidBadWifi = false;
         tracker.reevaluate();
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
 
         // Switch back to a restrictive carrier.
         tracker.configRestrictsAvoidBadWifi = true;
         tracker.reevaluate();
-        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
 
         // Simulate the user selecting "switch" on the dialog, and check that we switch to cell.
         mCm.setAvoidUnvalidated(wifiNetwork);
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
@@ -2794,9 +2827,8 @@
         mWiFiNetworkAgent.disconnect();
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
-        validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
-        validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi and expect the dialog to appear.
@@ -2810,7 +2842,7 @@
         tracker.reevaluate();
 
         // We now switch to cell.
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
@@ -2821,17 +2853,17 @@
         // We switch to wifi and then to cell.
         Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
         tracker.reevaluate();
-        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
         Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
         tracker.reevaluate();
-        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
 
         // If cell goes down, we switch to wifi.
         mCellNetworkAgent.disconnect();
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         validatedWifiCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(cellNetworkCallback);
@@ -2873,7 +2905,7 @@
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, timeoutMs);
+        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, timeoutMs);
 
         // pass timeout and validate that UNAVAILABLE is not called
         networkCallback.assertNoCallback();
@@ -2894,7 +2926,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
         final int assertTimeoutMs = 100;
-        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, assertTimeoutMs);
+        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, assertTimeoutMs);
         mWiFiNetworkAgent.disconnect();
         networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
@@ -3381,7 +3413,7 @@
 
         // Bring up wifi aware network.
         wifiAware.connect(false, false);
-        callback.expectAvailableCallbacks(wifiAware);
+        callback.expectAvailableCallbacksUnvalidated(wifiAware);
 
         assertNull(mCm.getActiveNetworkInfo());
         assertNull(mCm.getActiveNetwork());
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 24b28dd..7cffeea 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -17,6 +17,7 @@
 #include "ResourceParser.h"
 
 #include <functional>
+#include <limits>
 #include <sstream>
 
 #include "android-base/logging.h"
@@ -987,8 +988,7 @@
     type_mask = ParseFormatAttribute(maybe_format.value());
     if (type_mask == 0) {
       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
-                   << "invalid attribute format '" << maybe_format.value()
-                   << "'");
+                   << "invalid attribute format '" << maybe_format.value() << "'");
       return false;
     }
   }
@@ -1000,8 +1000,7 @@
     if (!min_str.empty()) {
       std::u16string min_str16 = util::Utf8ToUtf16(min_str);
       android::Res_value value;
-      if (android::ResTable::stringToInt(min_str16.data(), min_str16.size(),
-                                         &value)) {
+      if (android::ResTable::stringToInt(min_str16.data(), min_str16.size(), &value)) {
         maybe_min = static_cast<int32_t>(value.data);
       }
     }
@@ -1018,8 +1017,7 @@
     if (!max_str.empty()) {
       std::u16string max_str16 = util::Utf8ToUtf16(max_str);
       android::Res_value value;
-      if (android::ResTable::stringToInt(max_str16.data(), max_str16.size(),
-                                         &value)) {
+      if (android::ResTable::stringToInt(max_str16.data(), max_str16.size(), &value)) {
         maybe_max = static_cast<int32_t>(value.data);
       }
     }
@@ -1061,8 +1059,7 @@
     const Source item_source = source_.WithLine(parser->line_number());
     const std::string& element_namespace = parser->element_namespace();
     const std::string& element_name = parser->element_name();
-    if (element_namespace.empty() &&
-        (element_name == "flag" || element_name == "enum")) {
+    if (element_namespace.empty() && (element_name == "flag" || element_name == "enum")) {
       if (element_name == "enum") {
         if (type_mask & android::ResTable_map::TYPE_FLAGS) {
           diag_->Error(DiagMessage(item_source)
@@ -1120,17 +1117,12 @@
     return false;
   }
 
-  std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
+  std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(
+      type_mask ? type_mask : uint32_t{android::ResTable_map::TYPE_ANY});
+  attr->SetWeak(weak);
   attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
-  attr->type_mask =
-      type_mask ? type_mask : uint32_t(android::ResTable_map::TYPE_ANY);
-  if (maybe_min) {
-    attr->min_int = maybe_min.value();
-  }
-
-  if (maybe_max) {
-    attr->max_int = maybe_max.value();
-  }
+  attr->min_int = maybe_min.value_or_default(std::numeric_limits<int32_t>::min());
+  attr->max_int = maybe_max.value_or_default(std::numeric_limits<int32_t>::max());
   out_resource->value = std::move(attr);
   return true;
 }
@@ -1445,11 +1437,9 @@
     const std::string& element_namespace = parser->element_namespace();
     const std::string& element_name = parser->element_name();
     if (element_namespace.empty() && element_name == "attr") {
-      Maybe<StringPiece> maybe_name =
-          xml::FindNonEmptyAttribute(parser, "name");
+      Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
       if (!maybe_name) {
-        diag_->Error(DiagMessage(item_source)
-                     << "<attr> tag must have a 'name' attribute");
+        diag_->Error(DiagMessage(item_source) << "<attr> tag must have a 'name' attribute");
         error = true;
         continue;
       }
@@ -1457,8 +1447,7 @@
       // If this is a declaration, the package name may be in the name. Separate
       // these out.
       // Eg. <attr name="android:text" />
-      Maybe<Reference> maybe_ref =
-          ResourceUtils::ParseXmlAttributeName(maybe_name.value());
+      Maybe<Reference> maybe_ref = ResourceUtils::ParseXmlAttributeName(maybe_name.value());
       if (!maybe_ref) {
         diag_->Error(DiagMessage(item_source) << "<attr> tag has invalid name '"
                                               << maybe_name.value() << "'");
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 3172892..9905f82 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -262,9 +262,8 @@
   // attributes all-over, we do special handling to see
   // which definition sticks.
   //
-  if (existing_attr->type_mask == incoming_attr->type_mask) {
-    // The two attributes are both DECLs, but they are plain attributes
-    // with the same formats.
+  if (existing_attr->IsCompatibleWith(*incoming_attr)) {
+    // The two attributes are both DECLs, but they are plain attributes with compatible formats.
     // Keep the strongest one.
     return existing_attr->IsWeak() ? CollisionResult::kTakeNew : CollisionResult::kKeepOriginal;
   }
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index eaa2d0b..95e30c4 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -81,10 +81,7 @@
   DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
 };
 
-/**
- * Represents a resource entry, which may have
- * varying values for each defined configuration.
- */
+// Represents a resource entry, which may have varying values for each defined configuration.
 class ResourceEntry {
  public:
   // The name of the resource. Immutable, as this determines the order of this resource
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index eb75f94..7fa8ea2 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -102,23 +102,37 @@
 TEST(ResourceTableTest, OverrideWeakResourceValue) {
   ResourceTable table;
 
-  ASSERT_TRUE(table.AddResource(
-      test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
-      util::make_unique<Attribute>(true), test::GetDiagnostics()));
+  ASSERT_TRUE(table.AddResource(test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
+                                test::AttributeBuilder().SetWeak(true).Build(),
+                                test::GetDiagnostics()));
 
   Attribute* attr = test::GetValue<Attribute>(&table, "android:attr/foo");
   ASSERT_THAT(attr, NotNull());
   EXPECT_TRUE(attr->IsWeak());
 
-  ASSERT_TRUE(table.AddResource(
-      test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
-      util::make_unique<Attribute>(false), test::GetDiagnostics()));
+  ASSERT_TRUE(table.AddResource(test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
+                                util::make_unique<Attribute>(), test::GetDiagnostics()));
 
   attr = test::GetValue<Attribute>(&table, "android:attr/foo");
   ASSERT_THAT(attr, NotNull());
   EXPECT_FALSE(attr->IsWeak());
 }
 
+TEST(ResourceTableTest, AllowCompatibleDuplicateAttributes) {
+  ResourceTable table;
+
+  const ResourceName name = test::ParseNameOrDie("android:attr/foo");
+  Attribute attr_one(android::ResTable_map::TYPE_STRING);
+  attr_one.SetWeak(true);
+  Attribute attr_two(android::ResTable_map::TYPE_STRING | android::ResTable_map::TYPE_REFERENCE);
+  attr_two.SetWeak(true);
+
+  ASSERT_TRUE(table.AddResource(name, ConfigDescription{}, "",
+                                util::make_unique<Attribute>(attr_one), test::GetDiagnostics()));
+  ASSERT_TRUE(table.AddResource(name, ConfigDescription{}, "",
+                                util::make_unique<Attribute>(attr_two), test::GetDiagnostics()));
+}
+
 TEST(ResourceTableTest, ProductVaryingValues) {
   ResourceTable table;
 
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index e637c3e..cb786d3 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -179,12 +179,11 @@
 }
 
 TEST(ResourceUtilsTest, ParseEmptyFlag) {
-  std::unique_ptr<Attribute> attr =
-      test::AttributeBuilder(false)
-          .SetTypeMask(ResTable_map::TYPE_FLAGS)
-          .AddItem("one", 0x01)
-          .AddItem("two", 0x02)
-          .Build();
+  std::unique_ptr<Attribute> attr = test::AttributeBuilder()
+                                        .SetTypeMask(ResTable_map::TYPE_FLAGS)
+                                        .AddItem("one", 0x01)
+                                        .AddItem("two", 0x02)
+                                        .Build();
 
   std::unique_ptr<BinaryPrimitive> result = ResourceUtils::TryParseFlagSymbol(attr.get(), "");
   ASSERT_THAT(result, NotNull());
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index a782cd3..77cee06 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -507,17 +507,10 @@
   }
 }
 
-Attribute::Attribute()
-    : type_mask(0u),
-      min_int(std::numeric_limits<int32_t>::min()),
-      max_int(std::numeric_limits<int32_t>::max()) {
-}
-
-Attribute::Attribute(bool w, uint32_t t)
+Attribute::Attribute(uint32_t t)
     : type_mask(t),
       min_int(std::numeric_limits<int32_t>::min()),
       max_int(std::numeric_limits<int32_t>::max()) {
-  weak_ = w;
 }
 
 std::ostream& operator<<(std::ostream& out, const Attribute::Symbol& s) {
@@ -568,6 +561,20 @@
                     });
 }
 
+bool Attribute::IsCompatibleWith(const Attribute& attr) const {
+  // If the high bits are set on any of these attribute type masks, then they are incompatible.
+  // We don't check that flags and enums are identical.
+  if ((type_mask & ~android::ResTable_map::TYPE_ANY) != 0 ||
+      (attr.type_mask & ~android::ResTable_map::TYPE_ANY) != 0) {
+    return false;
+  }
+
+  // Every attribute accepts a reference.
+  uint32_t this_type_mask = type_mask | android::ResTable_map::TYPE_REFERENCE;
+  uint32_t that_type_mask = attr.type_mask | android::ResTable_map::TYPE_REFERENCE;
+  return this_type_mask == that_type_mask;
+}
+
 Attribute* Attribute::Clone(StringPool* /*new_pool*/) const {
   return new Attribute(*this);
 }
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index b2ec8bdd..6371c4c 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -300,10 +300,15 @@
   int32_t max_int;
   std::vector<Symbol> symbols;
 
-  Attribute();
-  explicit Attribute(bool w, uint32_t t = 0u);
+  explicit Attribute(uint32_t t = 0u);
 
   bool Equals(const Value* value) const override;
+
+  // Returns true if this Attribute's format is compatible with the given Attribute. The basic
+  // rule is that TYPE_REFERENCE can be ignored for both of the Attributes, and TYPE_FLAGS and
+  // TYPE_ENUMS are never compatible.
+  bool IsCompatibleWith(const Attribute& attr) const;
+
   Attribute* Clone(StringPool* new_pool) const override;
   std::string MaskString() const;
   void Print(std::ostream* out) const override;
diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp
index a80a9dc..c4a1108 100644
--- a/tools/aapt2/ResourceValues_test.cpp
+++ b/tools/aapt2/ResourceValues_test.cpp
@@ -24,6 +24,18 @@
 
 namespace aapt {
 
+namespace {
+
+// Attribute types.
+constexpr const uint32_t TYPE_DIMENSION = android::ResTable_map::TYPE_DIMENSION;
+constexpr const uint32_t TYPE_ENUM = android::ResTable_map::TYPE_ENUM;
+constexpr const uint32_t TYPE_FLAGS = android::ResTable_map::TYPE_FLAGS;
+constexpr const uint32_t TYPE_INTEGER = android::ResTable_map::TYPE_INTEGER;
+constexpr const uint32_t TYPE_REFERENCE = android::Res_value::TYPE_REFERENCE;
+constexpr const uint32_t TYPE_STRING = android::ResTable_map::TYPE_STRING;
+
+}  // namespace
+
 TEST(ResourceValuesTest, PluralEquals) {
   StringPool pool;
 
@@ -206,23 +218,19 @@
   android::Res_value value = {};
   ASSERT_TRUE(Reference().Flatten(&value));
 
-  EXPECT_EQ(android::Res_value::TYPE_REFERENCE, value.dataType);
-  EXPECT_EQ(0x0u, value.data);
+  EXPECT_THAT(value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
+  EXPECT_THAT(value.data, Eq(0u));
 }
 
 TEST(ResourcesValuesTest, AttributeMatches) {
-  constexpr const uint32_t TYPE_DIMENSION = android::ResTable_map::TYPE_DIMENSION;
-  constexpr const uint32_t TYPE_ENUM = android::ResTable_map::TYPE_ENUM;
-  constexpr const uint32_t TYPE_FLAGS = android::ResTable_map::TYPE_FLAGS;
-  constexpr const uint32_t TYPE_INTEGER = android::ResTable_map::TYPE_INTEGER;
   constexpr const uint8_t TYPE_INT_DEC = android::Res_value::TYPE_INT_DEC;
 
-  Attribute attr1(false /*weak*/, TYPE_DIMENSION);
+  Attribute attr1(TYPE_DIMENSION);
   EXPECT_FALSE(attr1.Matches(*ResourceUtils::TryParseColor("#7fff00")));
   EXPECT_TRUE(attr1.Matches(*ResourceUtils::TryParseFloat("23dp")));
   EXPECT_TRUE(attr1.Matches(*ResourceUtils::TryParseReference("@android:string/foo")));
 
-  Attribute attr2(false /*weak*/, TYPE_INTEGER | TYPE_ENUM);
+  Attribute attr2(TYPE_INTEGER | TYPE_ENUM);
   attr2.min_int = 0;
   attr2.symbols.push_back(Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")),
                                             static_cast<uint32_t>(-1)});
@@ -231,7 +239,7 @@
   EXPECT_TRUE(attr2.Matches(BinaryPrimitive(TYPE_INT_DEC, 1u)));
   EXPECT_FALSE(attr2.Matches(BinaryPrimitive(TYPE_INT_DEC, static_cast<uint32_t>(-2))));
 
-  Attribute attr3(false /*weak*/, TYPE_INTEGER | TYPE_FLAGS);
+  Attribute attr3(TYPE_INTEGER | TYPE_FLAGS);
   attr3.max_int = 100;
   attr3.symbols.push_back(
       Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
@@ -251,11 +259,33 @@
   // Not a flag and greater than max_int.
   EXPECT_FALSE(attr3.Matches(BinaryPrimitive(TYPE_INT_DEC, 127u)));
 
-  Attribute attr4(false /*weak*/, TYPE_ENUM);
+  Attribute attr4(TYPE_ENUM);
   attr4.symbols.push_back(
       Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
   EXPECT_TRUE(attr4.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x01u)));
   EXPECT_FALSE(attr4.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x02u)));
 }
 
+TEST(ResourcesValuesTest, AttributeIsCompatible) {
+  Attribute attr_one(TYPE_STRING | TYPE_REFERENCE);
+  Attribute attr_two(TYPE_STRING);
+  Attribute attr_three(TYPE_ENUM);
+  Attribute attr_four(TYPE_REFERENCE);
+
+  EXPECT_TRUE(attr_one.IsCompatibleWith(attr_one));
+  EXPECT_TRUE(attr_one.IsCompatibleWith(attr_two));
+  EXPECT_FALSE(attr_one.IsCompatibleWith(attr_three));
+  EXPECT_FALSE(attr_one.IsCompatibleWith(attr_four));
+
+  EXPECT_TRUE(attr_two.IsCompatibleWith(attr_one));
+  EXPECT_TRUE(attr_two.IsCompatibleWith(attr_two));
+  EXPECT_FALSE(attr_two.IsCompatibleWith(attr_three));
+  EXPECT_FALSE(attr_two.IsCompatibleWith(attr_four));
+
+  EXPECT_FALSE(attr_three.IsCompatibleWith(attr_one));
+  EXPECT_FALSE(attr_three.IsCompatibleWith(attr_two));
+  EXPECT_FALSE(attr_three.IsCompatibleWith(attr_three));
+  EXPECT_FALSE(attr_three.IsCompatibleWith(attr_four));
+}
+
 } // namespace aapt
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 3bec082..101f74e 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -772,8 +772,8 @@
       if (*type != ResourceType::kRaw) {
         if (path_data.extension == "xml") {
           compile_func = &CompileXml;
-        } else if (!options.no_png_crunch &&
-                   (path_data.extension == "png" || path_data.extension == "9.png")) {
+        } else if ((!options.no_png_crunch && path_data.extension == "png") ||
+                   path_data.extension == "9.png") {
           compile_func = &CompilePng;
         }
       }
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 72e07dc..c9e272c 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -388,10 +388,8 @@
   // generated from the attribute definitions themselves (b/62028956).
   if (const SymbolTable::Symbol* s = symm->FindById(R::attr::paddingHorizontal)) {
     std::vector<ReplacementAttr> replacements{
-        {"paddingLeft", R::attr::paddingLeft,
-         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
-        {"paddingRight", R::attr::paddingRight,
-         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+        {"paddingLeft", R::attr::paddingLeft, Attribute(android::ResTable_map::TYPE_DIMENSION)},
+        {"paddingRight", R::attr::paddingRight, Attribute(android::ResTable_map::TYPE_DIMENSION)},
     };
     rules_[R::attr::paddingHorizontal] =
         util::make_unique<DegradeToManyRule>(std::move(replacements));
@@ -399,10 +397,8 @@
 
   if (const SymbolTable::Symbol* s = symm->FindById(R::attr::paddingVertical)) {
     std::vector<ReplacementAttr> replacements{
-        {"paddingTop", R::attr::paddingTop,
-         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
-        {"paddingBottom", R::attr::paddingBottom,
-         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+        {"paddingTop", R::attr::paddingTop, Attribute(android::ResTable_map::TYPE_DIMENSION)},
+        {"paddingBottom", R::attr::paddingBottom, Attribute(android::ResTable_map::TYPE_DIMENSION)},
     };
     rules_[R::attr::paddingVertical] =
         util::make_unique<DegradeToManyRule>(std::move(replacements));
@@ -411,9 +407,9 @@
   if (const SymbolTable::Symbol* s = symm->FindById(R::attr::layout_marginHorizontal)) {
     std::vector<ReplacementAttr> replacements{
         {"layout_marginLeft", R::attr::layout_marginLeft,
-         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+         Attribute(android::ResTable_map::TYPE_DIMENSION)},
         {"layout_marginRight", R::attr::layout_marginRight,
-         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+         Attribute(android::ResTable_map::TYPE_DIMENSION)},
     };
     rules_[R::attr::layout_marginHorizontal] =
         util::make_unique<DegradeToManyRule>(std::move(replacements));
@@ -422,9 +418,9 @@
   if (const SymbolTable::Symbol* s = symm->FindById(R::attr::layout_marginVertical)) {
     std::vector<ReplacementAttr> replacements{
         {"layout_marginTop", R::attr::layout_marginTop,
-         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+         Attribute(android::ResTable_map::TYPE_DIMENSION)},
         {"layout_marginBottom", R::attr::layout_marginBottom,
-         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+         Attribute(android::ResTable_map::TYPE_DIMENSION)},
     };
     rules_[R::attr::layout_marginVertical] =
         util::make_unique<DegradeToManyRule>(std::move(replacements));
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 8d079ff..8215ddf 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -504,8 +504,8 @@
 std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr(const ResourceNameRef& name,
                                                            const ConfigDescription& config,
                                                            const ResTable_map_entry* map) {
-  const bool is_weak = (util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0;
-  std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak);
+  std::unique_ptr<Attribute> attr = util::make_unique<Attribute>();
+  attr->SetWeak((util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0);
 
   // First we must discover what type of attribute this is. Find the type mask.
   auto type_mask_iter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 51ccdc7..bab7010 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -215,7 +215,7 @@
 }
 
 TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
-  Attribute attr(false);
+  Attribute attr;
   attr.type_mask = android::ResTable_map::TYPE_INTEGER;
   attr.min_int = 10;
   attr.max_int = 23;
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 3d6975d..d8635a9 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -635,8 +635,7 @@
     switch (pb_compound_value.value_case()) {
       case pb::CompoundValue::kAttr: {
         const pb::Attribute& pb_attr = pb_compound_value.attr();
-        std::unique_ptr<Attribute> attr = util::make_unique<Attribute>();
-        attr->type_mask = pb_attr.format_flags();
+        std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(pb_attr.format_flags());
         attr->min_int = pb_attr.min_int();
         attr->max_int = pb_attr.max_int();
         for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbol()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index d7f83fd..ccba5c6 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -180,7 +180,7 @@
   attr.name = "name";
   attr.namespace_uri = xml::kSchemaAndroid;
   attr.value = "23dp";
-  attr.compiled_attribute = xml::AaptAttribute({}, ResourceId(0x01010000));
+  attr.compiled_attribute = xml::AaptAttribute(Attribute{}, ResourceId(0x01010000));
   attr.compiled_value =
       ResourceUtils::TryParseItemForAttribute(attr.value, android::ResTable_map::TYPE_DIMENSION);
   attr.compiled_value->SetSource(Source().WithLine(25));
diff --git a/tools/aapt2/integration-tests/BasicTest/Android.mk b/tools/aapt2/integration-tests/BasicTest/Android.mk
new file mode 100644
index 0000000..6d2aac6
--- /dev/null
+++ b/tools/aapt2/integration-tests/BasicTest/Android.mk
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_PACKAGE_NAME := AaptBasicTest
+LOCAL_MODULE_TAGS := tests
+include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/BasicTest/AndroidManifest.xml b/tools/aapt2/integration-tests/BasicTest/AndroidManifest.xml
new file mode 100644
index 0000000..3743c40
--- /dev/null
+++ b/tools/aapt2/integration-tests/BasicTest/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.aapt.basic">
+
+    <uses-sdk android:minSdkVersion="21" />
+</manifest>
diff --git a/tools/aapt2/integration-tests/BasicTest/res/values/values.xml b/tools/aapt2/integration-tests/BasicTest/res/values/values.xml
new file mode 100644
index 0000000..8d6bb43
--- /dev/null
+++ b/tools/aapt2/integration-tests/BasicTest/res/values/values.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <declare-styleable name="AttrConflictStyleableOne">
+        <attr name="format_conflict" format="string|reference" />
+    </declare-styleable>
+
+    <declare-styleable name="AttrConflictStyleableTwo">
+        <attr name="format_conflict" format="string" />
+    </declare-styleable>
+</resources>
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 5beb594..e449546 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -57,7 +57,7 @@
           .SetPackageId("android", 0x01)
           .AddSimple("android:id/hey-man", ResourceId(0x01020000))
           .AddValue("android:attr/cool.attr", ResourceId(0x01010000),
-                    test::AttributeBuilder(false).Build())
+                    test::AttributeBuilder().Build())
           .AddValue("android:styleable/hey.dude", ResourceId(0x01030000),
                     test::StyleableBuilder()
                         .AddItem("android:attr/cool.attr", ResourceId(0x01010000))
@@ -229,10 +229,8 @@
       test::ResourceTableBuilder()
           .SetPackageId("android", 0x01)
           .SetPackageId("com.lib", 0x02)
-          .AddValue("android:attr/bar", ResourceId(0x01010000),
-                    test::AttributeBuilder(false).Build())
-          .AddValue("com.lib:attr/bar", ResourceId(0x02010000),
-                    test::AttributeBuilder(false).Build())
+          .AddValue("android:attr/bar", ResourceId(0x01010000), test::AttributeBuilder().Build())
+          .AddValue("com.lib:attr/bar", ResourceId(0x02010000), test::AttributeBuilder().Build())
           .AddValue("android:styleable/foo", ResourceId(0x01030000),
                     test::StyleableBuilder()
                         .AddItem("android:attr/bar", ResourceId(0x01010000))
@@ -290,7 +288,7 @@
 TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {}
 
 TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) {
-  Attribute attr(false);
+  Attribute attr;
   attr.SetComment(StringPiece("This is an attribute"));
 
   Styleable styleable;
@@ -375,7 +373,7 @@
 }
 
 TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) {
-  Attribute attr(false);
+  Attribute attr;
   attr.SetComment(StringPiece("removed"));
 
   std::unique_ptr<ResourceTable> table =
@@ -413,7 +411,7 @@
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
           .SetPackageId("android", 0x00)
-          .AddValue("android:attr/foo", ResourceId(0x00010000), util::make_unique<Attribute>(false))
+          .AddValue("android:attr/foo", ResourceId(0x00010000), util::make_unique<Attribute>())
           .AddValue("android:id/foo", ResourceId(0x00020000), util::make_unique<Id>())
           .AddValue(
               "android:style/foo", ResourceId(0x00030000),
diff --git a/tools/aapt2/link/XmlCompatVersioner_test.cpp b/tools/aapt2/link/XmlCompatVersioner_test.cpp
index 29ad25f..1ed4536 100644
--- a/tools/aapt2/link/XmlCompatVersioner_test.cpp
+++ b/tools/aapt2/link/XmlCompatVersioner_test.cpp
@@ -54,17 +54,17 @@
             .AddSymbolSource(
                 test::StaticSymbolSourceBuilder()
                     .AddPublicSymbol("android:attr/paddingLeft", R::attr::paddingLeft,
-                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
+                                     util::make_unique<Attribute>(TYPE_DIMENSION))
                     .AddPublicSymbol("android:attr/paddingRight", R::attr::paddingRight,
-                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
+                                     util::make_unique<Attribute>(TYPE_DIMENSION))
                     .AddPublicSymbol("android:attr/progressBarPadding", R::attr::progressBarPadding,
-                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
+                                     util::make_unique<Attribute>(TYPE_DIMENSION))
                     .AddPublicSymbol("android:attr/paddingStart", R::attr::paddingStart,
-                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
+                                     util::make_unique<Attribute>(TYPE_DIMENSION))
                     .AddPublicSymbol("android:attr/paddingHorizontal", R::attr::paddingHorizontal,
-                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
+                                     util::make_unique<Attribute>(TYPE_DIMENSION))
                     .AddSymbol("com.app:attr/foo", ResourceId(0x7f010000),
-                               util::make_unique<Attribute>(false, TYPE_STRING))
+                               util::make_unique<Attribute>(TYPE_STRING))
                     .Build())
             .Build();
   }
@@ -126,9 +126,8 @@
   XmlCompatVersioner::Rules rules;
   rules[R::attr::paddingHorizontal] =
       util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>(
-          {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)},
-           ReplacementAttr{"paddingRight", R::attr::paddingRight,
-                           Attribute(false, TYPE_DIMENSION)}}));
+          {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(TYPE_DIMENSION)},
+           ReplacementAttr{"paddingRight", R::attr::paddingRight, Attribute(TYPE_DIMENSION)}}));
 
   const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
 
@@ -187,12 +186,11 @@
   XmlCompatVersioner::Rules rules;
   rules[R::attr::progressBarPadding] =
       util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>(
-          {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)},
-           ReplacementAttr{"paddingRight", R::attr::paddingRight,
-                           Attribute(false, TYPE_DIMENSION)}}));
+          {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(TYPE_DIMENSION)},
+           ReplacementAttr{"paddingRight", R::attr::paddingRight, Attribute(TYPE_DIMENSION)}}));
   rules[R::attr::paddingHorizontal] =
       util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>({ReplacementAttr{
-          "progressBarPadding", R::attr::progressBarPadding, Attribute(false, TYPE_DIMENSION)}}));
+          "progressBarPadding", R::attr::progressBarPadding, Attribute(TYPE_DIMENSION)}}));
 
   const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
 
@@ -267,9 +265,8 @@
   XmlCompatVersioner::Rules rules;
   rules[R::attr::paddingHorizontal] =
       util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>(
-          {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)},
-           ReplacementAttr{"paddingRight", R::attr::paddingRight,
-                           Attribute(false, TYPE_DIMENSION)}}));
+          {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(TYPE_DIMENSION)},
+           ReplacementAttr{"paddingRight", R::attr::paddingRight, Attribute(TYPE_DIMENSION)}}));
 
   const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
 
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index 8852c8e..160ff92 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -79,16 +79,15 @@
 
   void Visit(xml::Element* el) override {
     // The default Attribute allows everything except enums or flags.
-    constexpr const static uint32_t kDefaultTypeMask =
-        0xffffffffu & ~(android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS);
-    const static Attribute kDefaultAttribute(true /* weak */, kDefaultTypeMask);
+    Attribute default_attribute(android::ResTable_map::TYPE_ANY);
+    default_attribute.SetWeak(true);
 
     const Source source = source_.WithLine(el->line_number);
     for (xml::Attribute& attr : el->attributes) {
       // If the attribute has no namespace, interpret values as if
       // they were assigned to the default Attribute.
 
-      const Attribute* attribute = &kDefaultAttribute;
+      const Attribute* attribute = &default_attribute;
 
       if (Maybe<xml::ExtractedPackage> maybe_package =
               xml::ExtractPackageFromNamespace(attr.namespace_uri)) {
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 3cae0e8..2e97a2f 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -243,7 +243,7 @@
   // Check to see if it is an attribute.
   for (size_t i = 0; i < (size_t)count; i++) {
     if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
-      s->attribute = std::make_shared<Attribute>(false, entry[i].map.value.data);
+      s->attribute = std::make_shared<Attribute>(entry[i].map.value.data);
       break;
     }
   }
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 495a48a..c4eab12 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -156,8 +156,8 @@
   return util::make_unique<BinaryPrimitive>(value);
 }
 
-AttributeBuilder::AttributeBuilder(bool weak) : attr_(util::make_unique<Attribute>(weak)) {
-  attr_->type_mask = android::ResTable_map::TYPE_ANY;
+AttributeBuilder::AttributeBuilder()
+    : attr_(util::make_unique<Attribute>(android::ResTable_map::TYPE_ANY)) {
 }
 
 AttributeBuilder& AttributeBuilder::SetTypeMask(uint32_t typeMask) {
@@ -165,6 +165,11 @@
   return *this;
 }
 
+AttributeBuilder& AttributeBuilder::SetWeak(bool weak) {
+  attr_->SetWeak(weak);
+  return *this;
+}
+
 AttributeBuilder& AttributeBuilder::AddItem(const StringPiece& name, uint32_t value) {
   attr_->symbols.push_back(
       Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value});
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 0d7451b..fd5262a 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -113,8 +113,9 @@
 
 class AttributeBuilder {
  public:
-  explicit AttributeBuilder(bool weak = false);
+  AttributeBuilder();
   AttributeBuilder& SetTypeMask(uint32_t typeMask);
+  AttributeBuilder& SetWeak(bool weak);
   AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value);
   std::unique_ptr<Attribute> Build();
 
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 421e545..399b0c6 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -100,9 +100,11 @@
         self.typ = raw[0]
         self.name = raw[1]
         self.args = []
+        self.throws = []
+        target = self.args
         for r in raw[2:]:
-            if r == "throws": break
-            self.args.append(r)
+            if r == "throws": target = self.throws
+            else: target.append(r)
 
         # identity for compat purposes
         ident = self.raw
@@ -391,7 +393,7 @@
                         prefix = clazz.pkg.name + ".action"
                     expected = prefix + "." + f.name[7:]
                     if f.value != expected:
-                        error(clazz, f, "C4", "Inconsistent action value; expected %s" % (expected))
+                        error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
 
 
 def verify_extras(clazz):
@@ -421,7 +423,7 @@
                         prefix = clazz.pkg.name + ".extra"
                     expected = prefix + "." + f.name[6:]
                     if f.value != expected:
-                        error(clazz, f, "C4", "Inconsistent extra value; expected %s" % (expected))
+                        error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
 
 
 def verify_equals(clazz):
@@ -450,6 +452,10 @@
             (" final deprecated class " not in clazz.raw)):
             error(clazz, None, "FW8", "Parcelable classes must be final")
 
+        for c in clazz.ctors:
+            if c.args == ["android.os.Parcel"]:
+                error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
+
 
 def verify_protected(clazz):
     """Verify that no protected methods or fields are allowed."""
@@ -572,7 +578,7 @@
             if f.name == "SERVICE_INTERFACE":
                 found = True
                 if f.value != clazz.fullname:
-                    error(clazz, f, "C4", "Inconsistent interface constant; expected %s" % (clazz.fullname))
+                    error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
 
     if "extends android.content.ContentProvider" in clazz.raw:
         test_methods = True
@@ -584,7 +590,7 @@
             if f.name == "PROVIDER_INTERFACE":
                 found = True
                 if f.value != clazz.fullname:
-                    error(clazz, f, "C4", "Inconsistent interface constant; expected %s" % (clazz.fullname))
+                    error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
 
     if "extends android.content.BroadcastReceiver" in clazz.raw:
         test_methods = True
@@ -764,15 +770,19 @@
 def verify_exception(clazz):
     """Verifies that methods don't throw generic exceptions."""
     for m in clazz.methods:
-        if "throws java.lang.Exception" in m.raw or "throws java.lang.Throwable" in m.raw or "throws java.lang.Error" in m.raw:
-            error(clazz, m, "S1", "Methods must not throw generic exceptions")
+        for t in m.throws:
+            if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
+                error(clazz, m, "S1", "Methods must not throw generic exceptions")
 
-        if "throws android.os.RemoteException" in m.raw:
-            if clazz.name == "android.content.ContentProviderClient": continue
-            if clazz.name == "android.os.Binder": continue
-            if clazz.name == "android.os.IBinder": continue
+            if t in ["android.os.RemoteException"]:
+                if clazz.name == "android.content.ContentProviderClient": continue
+                if clazz.name == "android.os.Binder": continue
+                if clazz.name == "android.os.IBinder": continue
 
-            error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
+                error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
+
+            if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
+                warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
 
 
 def verify_google(clazz):
@@ -927,7 +937,8 @@
 
     found = {}
     by_name = collections.defaultdict(list)
-    for m in clazz.methods:
+    examine = clazz.ctors + clazz.methods
+    for m in examine:
         if m.name.startswith("unregister"): continue
         if m.name.startswith("remove"): continue
         if re.match("on[A-Z]+", m.name): continue
@@ -971,7 +982,7 @@
         for a in m.args:
             if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
                 found = True
-            elif found and a != "android.os.Handler" and a != "java.util.concurrent.Executor":
+            elif found:
                 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
 
 
@@ -1078,16 +1089,11 @@
         "java.nio.BufferOverflowException",
     ]
 
-    test = []
-    test.extend(clazz.ctors)
-    test.extend(clazz.methods)
-
-    for t in test:
-        if " throws " not in t.raw: continue
-        throws = t.raw[t.raw.index(" throws "):]
-        for b in banned:
-            if b in throws:
-                error(clazz, t, None, "Methods must not mention RuntimeException subclasses in throws clauses")
+    examine = clazz.ctors + clazz.methods
+    for m in examine:
+        for t in m.throws:
+            if t in banned:
+                error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
 
 
 def verify_error(clazz):
@@ -1233,6 +1239,58 @@
                 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
 
 
+def verify_user_handle(clazz):
+    """Methods taking UserHandle should be ForUser or AsUser."""
+    if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
+    if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
+    if clazz.fullname == "android.content.pm.LauncherApps": return
+    if clazz.fullname == "android.os.UserHandle": return
+    if clazz.fullname == "android.os.UserManager": return
+
+    for m in clazz.methods:
+        if m.name.endswith("AsUser") or m.name.endswith("ForUser"): continue
+        if re.match("on[A-Z]+", m.name): continue
+        if "android.os.UserHandle" in m.args:
+            warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' or 'queryFooForUser'")
+
+
+def verify_params(clazz):
+    """Parameter classes should be 'Params'."""
+    if clazz.name.endswith("Params"): return
+    if clazz.fullname == "android.app.ActivityOptions": return
+    if clazz.fullname == "android.app.BroadcastOptions": return
+    if clazz.fullname == "android.os.Bundle": return
+    if clazz.fullname == "android.os.BaseBundle": return
+    if clazz.fullname == "android.os.PersistableBundle": return
+
+    bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
+    for b in bad:
+        if clazz.name.endswith(b):
+            error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
+
+
+def verify_services(clazz):
+    """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
+    if clazz.fullname != "android.content.Context": return
+
+    for f in clazz.fields:
+        if f.typ != "java.lang.String": continue
+        found = re.match(r"([A-Z_]+)_SERVICE", f.name)
+        if found:
+            expected = found.group(1).lower()
+            if f.value != expected:
+                error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
+
+
+def verify_tense(clazz):
+    """Verify tenses of method names."""
+    if clazz.fullname.startswith("android.opengl"): return
+
+    for m in clazz.methods:
+        if m.name.endswith("Enable"):
+            warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
+
+
 def examine_clazz(clazz):
     """Find all style issues in the given class."""
 
@@ -1290,6 +1348,10 @@
     verify_member_name_not_kotlin_keyword(clazz)
     verify_method_name_not_kotlin_operator(clazz)
     verify_collections_over_arrays(clazz)
+    verify_user_handle(clazz)
+    verify_params(clazz)
+    verify_services(clazz)
+    verify_tense(clazz)
 
 
 def examine_stream(stream):
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 80853b1..0e57f7f 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "Collation.h"
+#include "frameworks/base/cmds/statsd/src/atoms.pb.h"
 
 #include <stdio.h>
 #include <map>
@@ -137,6 +138,16 @@
 }
 
 /**
+ * Gather the enums info.
+ */
+void collate_enums(const EnumDescriptor &enumDescriptor, AtomField *atomField) {
+    for (int i = 0; i < enumDescriptor.value_count(); i++) {
+        atomField->enumValues[enumDescriptor.value(i)->number()] =
+            enumDescriptor.value(i)->name().c_str();
+    }
+}
+
+/**
  * Gather the info about an atom proto.
  */
 int collate_atom(const Descriptor *atom, AtomDecl *atomDecl,
@@ -221,11 +232,7 @@
     if (javaType == JAVA_TYPE_ENUM) {
       // All enums are treated as ints when it comes to function signatures.
       signature->push_back(JAVA_TYPE_INT);
-      const EnumDescriptor *enumDescriptor = field->enum_type();
-      for (int i = 0; i < enumDescriptor->value_count(); i++) {
-        atField.enumValues[enumDescriptor->value(i)->number()] =
-            enumDescriptor->value(i)->name().c_str();
-      }
+      collate_enums(*field->enum_type(), &atField);
     } else {
       signature->push_back(javaType);
     }
@@ -235,6 +242,53 @@
   return errorCount;
 }
 
+// This function flattens the fields of the AttributionNode proto in an Atom proto and generates
+// the corresponding atom decl and signature.
+bool get_non_chained_node(const Descriptor *atom, AtomDecl *atomDecl,
+                          vector<java_type_t> *signature) {
+    // Build a sorted list of the fields. Descriptor has them in source file
+    // order.
+    map<int, const FieldDescriptor *> fields;
+    for (int j = 0; j < atom->field_count(); j++) {
+        const FieldDescriptor *field = atom->field(j);
+        fields[field->number()] = field;
+    }
+
+    AtomDecl attributionDecl;
+    vector<java_type_t> attributionSignature;
+    collate_atom(android::os::statsd::AttributionNode::descriptor(),
+                 &attributionDecl, &attributionSignature);
+
+    // Build the type signature and the atom data.
+    bool has_attribution_node = false;
+    for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
+        it != fields.end(); it++) {
+        const FieldDescriptor *field = it->second;
+        java_type_t javaType = java_type(field);
+        if (javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+            atomDecl->fields.insert(
+                atomDecl->fields.end(),
+                attributionDecl.fields.begin(), attributionDecl.fields.end());
+            signature->insert(
+                signature->end(),
+                attributionSignature.begin(), attributionSignature.end());
+            has_attribution_node = true;
+
+        } else {
+            AtomField atField(field->name(), javaType);
+            if (javaType == JAVA_TYPE_ENUM) {
+                // All enums are treated as ints when it comes to function signatures.
+                signature->push_back(JAVA_TYPE_INT);
+                collate_enums(*field->enum_type(), &atField);
+            } else {
+                signature->push_back(javaType);
+            }
+            atomDecl->fields.push_back(atField);
+        }
+    }
+    return has_attribution_node;
+}
+
 /**
  * Gather the info about the atoms.
  */
@@ -266,6 +320,13 @@
     errorCount += collate_atom(atom, &atomDecl, &signature);
     atoms->signatures.insert(signature);
     atoms->decls.insert(atomDecl);
+
+    AtomDecl nonChainedAtomDecl(atomField->number(), atomField->name(), atom->name());
+    vector<java_type_t> nonChainedSignature;
+    if (get_non_chained_node(atom, &nonChainedAtomDecl, &nonChainedSignature)) {
+        atoms->non_chained_signatures.insert(nonChainedSignature);
+        atoms->non_chained_decls.insert(nonChainedAtomDecl);
+    }
   }
 
   if (dbg) {
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index cd0625c..0455eca 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -32,6 +32,7 @@
 using std::string;
 using std::vector;
 using google::protobuf::Descriptor;
+using google::protobuf::FieldDescriptor;
 
 /**
  * The types for atom parameters.
@@ -93,14 +94,15 @@
 struct Atoms {
     set<vector<java_type_t>> signatures;
     set<AtomDecl> decls;
+    set<AtomDecl> non_chained_decls;
+    set<vector<java_type_t>> non_chained_signatures;
 };
 
 /**
  * Gather the information about the atoms.  Returns the number of errors.
  */
 int collate_atoms(const Descriptor* descriptor, Atoms* atoms);
-int collate_atom(const Descriptor *atom, AtomDecl *atomDecl,
-                 vector<java_type_t> *signature);
+int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, vector<java_type_t> *signature);
 
 }  // namespace stats_log_api_gen
 }  // namespace android
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index bbe6d63..e0e6b58 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -195,6 +195,47 @@
         fprintf(out, "\n");
     }
 
+    for (set<vector<java_type_t>>::const_iterator signature = atoms.non_chained_signatures.begin();
+        signature != atoms.non_chained_signatures.end(); signature++) {
+        int argIndex;
+
+        fprintf(out, "void\n");
+        fprintf(out, "stats_write_non_chained(int32_t code");
+        argIndex = 1;
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+            arg != signature->end(); arg++) {
+            fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
+            argIndex++;
+        }
+        fprintf(out, ")\n");
+
+        fprintf(out, "{\n");
+        argIndex = 1;
+        fprintf(out, "    android_log_event_list event(kStatsEventTag);\n");
+        fprintf(out, "    event << code;\n\n");
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+            arg != signature->end(); arg++) {
+            if (argIndex == 1) {
+                fprintf(out, "    event.begin();\n\n");
+                fprintf(out, "    event.begin();\n");
+            }
+            if (*arg == JAVA_TYPE_STRING) {
+                fprintf(out, "    if (arg%d == NULL) {\n", argIndex);
+                fprintf(out, "        arg%d = \"\";\n", argIndex);
+                fprintf(out, "    }\n");
+            }
+            fprintf(out, "    event << arg%d;\n", argIndex);
+            if (argIndex == 2) {
+                fprintf(out, "    event.end();\n\n");
+                fprintf(out, "    event.end();\n\n");
+            }
+            argIndex++;
+        }
+
+        fprintf(out, "    event.write(LOG_ID_STATS);\n");
+        fprintf(out, "}\n");
+        fprintf(out, "\n");
+    }
     // Print footer
     fprintf(out, "\n");
     fprintf(out, "} // namespace util\n");
@@ -203,6 +244,68 @@
     return 0;
 }
 
+void build_non_chained_decl_map(const Atoms& atoms,
+                                std::map<int, set<AtomDecl>::const_iterator>* decl_map){
+    for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin();
+        atom != atoms.non_chained_decls.end(); atom++) {
+        decl_map->insert(std::make_pair(atom->code, atom));
+    }
+}
+
+static void write_cpp_usage(
+    FILE* out, const string& method_name, const string& atom_code_name,
+    const AtomDecl& atom, const AtomDecl &attributionDecl) {
+    fprintf(out, "     * Usage: %s(StatsLog.%s", method_name.c_str(), atom_code_name.c_str());
+    for (vector<AtomField>::const_iterator field = atom.fields.begin();
+            field != atom.fields.end(); field++) {
+        if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+            for (auto chainField : attributionDecl.fields) {
+                if (chainField.javaType == JAVA_TYPE_STRING) {
+                    fprintf(out, ", const std::vector<%s>& %s",
+                         cpp_type_name(chainField.javaType),
+                         chainField.name.c_str());
+                } else {
+                    fprintf(out, ", const %s* %s, size_t %s_length",
+                         cpp_type_name(chainField.javaType),
+                         chainField.name.c_str(), chainField.name.c_str());
+                }
+            }
+        } else {
+            fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
+        }
+    }
+    fprintf(out, ");\n");
+}
+
+static void write_cpp_method_header(
+    FILE* out, const string& method_name, const set<vector<java_type_t>>& signatures,
+    const AtomDecl &attributionDecl) {
+    for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
+            signature != signatures.end(); signature++) {
+        fprintf(out, "void %s(int32_t code ", method_name.c_str());
+        int argIndex = 1;
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+            arg != signature->end(); arg++) {
+            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                for (auto chainField : attributionDecl.fields) {
+                    if (chainField.javaType == JAVA_TYPE_STRING) {
+                        fprintf(out, ", const std::vector<%s>& %s",
+                            cpp_type_name(chainField.javaType), chainField.name.c_str());
+                    } else {
+                        fprintf(out, ", const %s* %s, size_t %s_length",
+                            cpp_type_name(chainField.javaType),
+                            chainField.name.c_str(), chainField.name.c_str());
+                    }
+                }
+            } else {
+                fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
+            }
+            argIndex++;
+        }
+        fprintf(out, ");\n");
+
+    }
+}
 
 static int
 write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
@@ -228,6 +331,9 @@
     fprintf(out, " */\n");
     fprintf(out, "enum {\n");
 
+    std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
+    build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
+
     size_t i = 0;
     // Print constants
     for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
@@ -236,26 +342,13 @@
         fprintf(out, "\n");
         fprintf(out, "    /**\n");
         fprintf(out, "     * %s %s\n", atom->message.c_str(), atom->name.c_str());
-        fprintf(out, "     * Usage: stats_write(StatsLog.%s", constant.c_str());
-        for (vector<AtomField>::const_iterator field = atom->fields.begin();
-                field != atom->fields.end(); field++) {
-            if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
-                for (auto chainField : attributionDecl.fields) {
-                    if (chainField.javaType == JAVA_TYPE_STRING) {
-                        fprintf(out, ", const std::vector<%s>& %s",
-                             cpp_type_name(chainField.javaType),
-                             chainField.name.c_str());
-                    } else {
-                        fprintf(out, ", const %s* %s, size_t %s_length",
-                             cpp_type_name(chainField.javaType),
-                             chainField.name.c_str(), chainField.name.c_str());
-                    }
-                }
-            } else {
-                fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
-            }
+        write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl);
+
+        auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
+        if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
+            write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second,
+                attributionDecl);
         }
-        fprintf(out, ");\n");
         fprintf(out, "     */\n");
         char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
         fprintf(out, "    %s = %d%s\n", constant.c_str(), atom->code, comma);
@@ -274,30 +367,13 @@
     fprintf(out, "//\n");
     fprintf(out, "// Write methods\n");
     fprintf(out, "//\n");
-    for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
-            signature != atoms.signatures.end(); signature++) {
-        fprintf(out, "void stats_write(int32_t code ");
-        int argIndex = 1;
-        for (vector<java_type_t>::const_iterator arg = signature->begin();
-            arg != signature->end(); arg++) {
-            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
-                for (auto chainField : attributionDecl.fields) {
-                    if (chainField.javaType == JAVA_TYPE_STRING) {
-                        fprintf(out, ", const std::vector<%s>& %s",
-                            cpp_type_name(chainField.javaType), chainField.name.c_str());
-                    } else {
-                        fprintf(out, ", const %s* %s, size_t %s_length",
-                            cpp_type_name(chainField.javaType),
-                            chainField.name.c_str(), chainField.name.c_str());
-                    }
-                }
-            } else {
-                fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
-            }
-            argIndex++;
-        }
-        fprintf(out, ");\n");
-    }
+    write_cpp_method_header(out, "stats_write", atoms.signatures, attributionDecl);
+
+    fprintf(out, "//\n");
+    fprintf(out, "// Write flattened methods\n");
+    fprintf(out, "//\n");
+    write_cpp_method_header(out, "stats_write_non_chained", atoms.non_chained_signatures,
+        attributionDecl);
 
     fprintf(out, "\n");
     fprintf(out, "} // namespace util\n");
@@ -306,6 +382,49 @@
     return 0;
 }
 
+static void write_java_usage(
+    FILE* out, const string& method_name, const string& atom_code_name,
+    const AtomDecl& atom, const AtomDecl &attributionDecl) {
+    fprintf(out, "     * Usage: StatsLog.%s(StatsLog.%s",
+        method_name.c_str(), atom_code_name.c_str());
+    for (vector<AtomField>::const_iterator field = atom.fields.begin();
+        field != atom.fields.end(); field++) {
+        if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+            for (auto chainField : attributionDecl.fields) {
+                fprintf(out, ", %s[] %s",
+                    java_type_name(chainField.javaType), chainField.name.c_str());
+            }
+        } else {
+            fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str());
+        }
+    }
+    fprintf(out, ");\n");
+}
+
+static void write_java_method(
+    FILE* out, const string& method_name, const set<vector<java_type_t>>& signatures,
+    const AtomDecl &attributionDecl) {
+    for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
+        signature != signatures.end(); signature++) {
+        fprintf(out, "    public static native void %s(int code", method_name.c_str());
+        int argIndex = 1;
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+            arg != signature->end(); arg++) {
+            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                for (auto chainField : attributionDecl.fields) {
+                    fprintf(out, ", %s[] %s",
+                        java_type_name(chainField.javaType), chainField.name.c_str());
+                }
+            } else {
+                fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
+            }
+            argIndex++;
+        }
+        fprintf(out, ");\n");
+    }
+}
+
+
 static int
 write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
 {
@@ -322,6 +441,9 @@
     fprintf(out, "public class StatsLogInternal {\n");
     fprintf(out, "    // Constants for atom codes.\n");
 
+    std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
+    build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
+
     // Print constants for the atom codes.
     for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
             atom != atoms.decls.end(); atom++) {
@@ -329,19 +451,12 @@
         fprintf(out, "\n");
         fprintf(out, "    /**\n");
         fprintf(out, "     * %s %s\n", atom->message.c_str(), atom->name.c_str());
-        fprintf(out, "     * Usage: StatsLog.write(StatsLog.%s", constant.c_str());
-        for (vector<AtomField>::const_iterator field = atom->fields.begin();
-            field != atom->fields.end(); field++) {
-            if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
-                for (auto chainField : attributionDecl.fields) {
-                    fprintf(out, ", %s[] %s",
-                        java_type_name(chainField.javaType), chainField.name.c_str());
-                }
-            } else {
-                fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str());
-            }
+        write_java_usage(out, "write", constant, *atom, attributionDecl);
+        auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
+        if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
+            write_java_usage(out, "write_non_chained", constant, *non_chained_decl->second,
+             attributionDecl);
         }
-        fprintf(out, ");\n");
         fprintf(out, "     */\n");
         fprintf(out, "    public static final int %s = %d;\n", constant.c_str(), atom->code);
     }
@@ -371,24 +486,8 @@
 
     // Print write methods
     fprintf(out, "    // Write methods\n");
-    for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
-        signature != atoms.signatures.end(); signature++) {
-        fprintf(out, "    public static native void write(int code");
-        int argIndex = 1;
-        for (vector<java_type_t>::const_iterator arg = signature->begin();
-            arg != signature->end(); arg++) {
-            if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
-                for (auto chainField : attributionDecl.fields) {
-                    fprintf(out, ", %s[] %s",
-                        java_type_name(chainField.javaType), chainField.name.c_str());
-                }
-            } else {
-                fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
-            }
-            argIndex++;
-        }
-        fprintf(out, ");\n");
-    }
+    write_java_method(out, "write", atoms.signatures, attributionDecl);
+    write_java_method(out, "write_non_chained", atoms.non_chained_signatures, attributionDecl);
 
     fprintf(out, "}\n");
 
@@ -431,9 +530,9 @@
 }
 
 static string
-jni_function_name(const vector<java_type_t>& signature)
+jni_function_name(const string& method_name, const vector<java_type_t>& signature)
 {
-    string result("StatsLog_write");
+    string result("StatsLog_" + method_name);
     for (vector<java_type_t>::const_iterator arg = signature.begin();
         arg != signature.end(); arg++) {
         switch (*arg) {
@@ -509,34 +608,17 @@
 }
 
 static int
-write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
+write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp_method_name,
+    const set<vector<java_type_t>>& signatures, const AtomDecl &attributionDecl)
 {
-    // Print prelude
-    fprintf(out, "// This file is autogenerated\n");
-    fprintf(out, "\n");
-
-    fprintf(out, "#include <statslog.h>\n");
-    fprintf(out, "\n");
-    fprintf(out, "#include <nativehelper/JNIHelp.h>\n");
-    fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n");
-    fprintf(out, "#include <utils/Vector.h>\n");
-    fprintf(out, "#include \"core_jni_helpers.h\"\n");
-    fprintf(out, "#include \"jni.h\"\n");
-    fprintf(out, "\n");
-    fprintf(out, "#define UNUSED  __attribute__((__unused__))\n");
-    fprintf(out, "\n");
-
-    fprintf(out, "namespace android {\n");
-    fprintf(out, "\n");
-
     // Print write methods
-    for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
-        signature != atoms.signatures.end(); signature++) {
+    for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
+        signature != signatures.end(); signature++) {
         int argIndex;
 
         fprintf(out, "static void\n");
         fprintf(out, "%s(JNIEnv* env, jobject clazz UNUSED, jint code",
-                jni_function_name(*signature).c_str());
+                jni_function_name(java_method_name, *signature).c_str());
         argIndex = 1;
         for (vector<java_type_t>::const_iterator arg = signature->begin();
                 arg != signature->end(); arg++) {
@@ -624,7 +706,7 @@
 
         // stats_write call
         argIndex = 1;
-        fprintf(out, "    android::util::stats_write(code");
+        fprintf(out, "    android::util::%s(code", cpp_method_name.c_str());
         for (vector<java_type_t>::const_iterator arg = signature->begin();
                 arg != signature->end(); arg++) {
             if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
@@ -675,17 +757,53 @@
         fprintf(out, "\n");
     }
 
+
+    return 0;
+}
+
+void write_jni_registration(FILE* out, const string& java_method_name,
+    const set<vector<java_type_t>>& signatures, const AtomDecl &attributionDecl) {
+    for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
+            signature != signatures.end(); signature++) {
+        fprintf(out, "    { \"%s\", \"%s\", (void*)%s },\n",
+            java_method_name.c_str(),
+            jni_function_signature(*signature, attributionDecl).c_str(),
+            jni_function_name(java_method_name, *signature).c_str());
+    }
+}
+
+static int
+write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
+{
+    // Print prelude
+    fprintf(out, "// This file is autogenerated\n");
+    fprintf(out, "\n");
+
+    fprintf(out, "#include <statslog.h>\n");
+    fprintf(out, "\n");
+    fprintf(out, "#include <nativehelper/JNIHelp.h>\n");
+    fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n");
+    fprintf(out, "#include <utils/Vector.h>\n");
+    fprintf(out, "#include \"core_jni_helpers.h\"\n");
+    fprintf(out, "#include \"jni.h\"\n");
+    fprintf(out, "\n");
+    fprintf(out, "#define UNUSED  __attribute__((__unused__))\n");
+    fprintf(out, "\n");
+
+    fprintf(out, "namespace android {\n");
+    fprintf(out, "\n");
+
+    write_stats_log_jni(out, "write", "stats_write", atoms.signatures, attributionDecl);
+    write_stats_log_jni(out, "write_non_chained", "stats_write_non_chained",
+        atoms.non_chained_signatures, attributionDecl);
+
     // Print registration function table
     fprintf(out, "/*\n");
     fprintf(out, " * JNI registration.\n");
     fprintf(out, " */\n");
     fprintf(out, "static const JNINativeMethod gRegisterMethods[] = {\n");
-    for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
-            signature != atoms.signatures.end(); signature++) {
-        fprintf(out, "    { \"write\", \"%s\", (void*)%s },\n",
-            jni_function_signature(*signature, attributionDecl).c_str(),
-            jni_function_name(*signature).c_str());
-    }
+    write_jni_registration(out, "write", atoms.signatures, attributionDecl);
+    write_jni_registration(out, "write_non_chained", atoms.non_chained_signatures, attributionDecl);
     fprintf(out, "};\n");
     fprintf(out, "\n");
 
@@ -699,11 +817,9 @@
 
     fprintf(out, "\n");
     fprintf(out, "} // namespace android\n");
-
     return 0;
 }
 
-
 static void
 print_usage()
 {
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 0dd964c..70d6ce4 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -28,7 +28,6 @@
 import android.net.wifi.ScanSettings;
 import android.net.wifi.ScanResult;
 import android.net.wifi.PasspointManagementObjectDefinition;
-import android.net.wifi.WifiConnectionStatistics;
 import android.net.wifi.WifiActivityEnergyInfo;
 import android.net.Network;
 
@@ -166,8 +165,6 @@
 
     void enableWifiConnectivityManager(boolean enabled);
 
-    WifiConnectionStatistics getConnectionStatistics();
-
     void disableEphemeralNetwork(String SSID);
 
     void factoryReset();
diff --git a/wifi/java/android/net/wifi/WifiConnectionStatistics.aidl b/wifi/java/android/net/wifi/WifiConnectionStatistics.aidl
deleted file mode 100644
index 601face..0000000
--- a/wifi/java/android/net/wifi/WifiConnectionStatistics.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * 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 android.net.wifi;
-
-parcelable WifiConnectionStatistics;
diff --git a/wifi/java/android/net/wifi/WifiConnectionStatistics.java b/wifi/java/android/net/wifi/WifiConnectionStatistics.java
deleted file mode 100644
index 1120c66..0000000
--- a/wifi/java/android/net/wifi/WifiConnectionStatistics.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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 android.net.wifi;
-
-import android.annotation.SystemApi;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import java.util.HashMap;
-
-/**
- * Wifi Connection Statistics: gather various stats regarding WiFi connections,
- * connection requests, auto-join
- * and WiFi usage.
- * @hide
- * @removed
- */
-@SystemApi
-public class WifiConnectionStatistics implements Parcelable {
-    private static final String TAG = "WifiConnnectionStatistics";
-
-    /**
-     *  history of past connection to untrusted SSID
-     *  Key = SSID
-     *  Value = num connection
-     */
-    public HashMap<String, WifiNetworkConnectionStatistics> untrustedNetworkHistory;
-
-    // Number of time we polled the chip and were on 5GHz
-    public int num5GhzConnected;
-
-    // Number of time we polled the chip and were on 2.4GHz
-    public int num24GhzConnected;
-
-    // Number autojoin attempts
-    public int numAutoJoinAttempt;
-
-    // Number auto-roam attempts
-    public int numAutoRoamAttempt;
-
-    // Number wifimanager join attempts
-    public int numWifiManagerJoinAttempt;
-
-    public WifiConnectionStatistics() {
-        untrustedNetworkHistory = new HashMap<String, WifiNetworkConnectionStatistics>();
-    }
-
-    public void incrementOrAddUntrusted(String SSID, int connection, int usage) {
-        WifiNetworkConnectionStatistics stats;
-        if (TextUtils.isEmpty(SSID))
-            return;
-        if (untrustedNetworkHistory.containsKey(SSID)) {
-            stats = untrustedNetworkHistory.get(SSID);
-            if (stats != null){
-                stats.numConnection = connection + stats.numConnection;
-                stats.numUsage = usage + stats.numUsage;
-            }
-        } else {
-            stats = new WifiNetworkConnectionStatistics(connection, usage);
-        }
-        if (stats != null) {
-            untrustedNetworkHistory.put(SSID, stats);
-        }
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sbuf = new StringBuilder();
-        sbuf.append("Connected on: 2.4Ghz=").append(num24GhzConnected);
-        sbuf.append(" 5Ghz=").append(num5GhzConnected).append("\n");
-        sbuf.append(" join=").append(numWifiManagerJoinAttempt);
-        sbuf.append("\\").append(numAutoJoinAttempt).append("\n");
-        sbuf.append(" roam=").append(numAutoRoamAttempt).append("\n");
-
-        for (String Key : untrustedNetworkHistory.keySet()) {
-            WifiNetworkConnectionStatistics stats = untrustedNetworkHistory.get(Key);
-            if (stats != null) {
-                sbuf.append(Key).append(" ").append(stats.toString()).append("\n");
-            }
-        }
-        return sbuf.toString();
-    }
-
-    /** copy constructor*/
-    public WifiConnectionStatistics(WifiConnectionStatistics source) {
-        untrustedNetworkHistory = new HashMap<String, WifiNetworkConnectionStatistics>();
-        if (source != null) {
-            untrustedNetworkHistory.putAll(source.untrustedNetworkHistory);
-        }
-    }
-
-    /** Implement the Parcelable interface */
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Implement the Parcelable interface */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(num24GhzConnected);
-        dest.writeInt(num5GhzConnected);
-        dest.writeInt(numAutoJoinAttempt);
-        dest.writeInt(numAutoRoamAttempt);
-        dest.writeInt(numWifiManagerJoinAttempt);
-
-        dest.writeInt(untrustedNetworkHistory.size());
-        for (String Key : untrustedNetworkHistory.keySet()) {
-            WifiNetworkConnectionStatistics num = untrustedNetworkHistory.get(Key);
-            dest.writeString(Key);
-            dest.writeInt(num.numConnection);
-            dest.writeInt(num.numUsage);
-
-        }
-    }
-
-    /** Implement the Parcelable interface */
-    public static final Creator<WifiConnectionStatistics> CREATOR =
-        new Creator<WifiConnectionStatistics>() {
-            public WifiConnectionStatistics createFromParcel(Parcel in) {
-                WifiConnectionStatistics stats = new WifiConnectionStatistics();
-                stats.num24GhzConnected = in.readInt();
-                stats.num5GhzConnected = in.readInt();
-                stats.numAutoJoinAttempt = in.readInt();
-                stats.numAutoRoamAttempt = in.readInt();
-                stats.numWifiManagerJoinAttempt = in.readInt();
-                int n = in.readInt();
-                while (n-- > 0) {
-                    String Key = in.readString();
-                    int numConnection = in.readInt();
-                    int numUsage = in.readInt();
-                    WifiNetworkConnectionStatistics st =
-                            new WifiNetworkConnectionStatistics(numConnection, numUsage);
-                    stats.untrustedNetworkHistory.put(Key, st);
-                }
-                return stats;
-            }
-
-            public WifiConnectionStatistics[] newArray(int size) {
-                return new WifiConnectionStatistics[size];
-            }
-        };
-}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 70e83db..aa75a07 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -999,20 +999,6 @@
     }
 
     /**
-     * @hide
-     * @removed
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_WIFI_CREDENTIAL)
-    public WifiConnectionStatistics getConnectionStatistics() {
-        try {
-            return mService.getConnectionStatistics();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Returns a WifiConfiguration matching this ScanResult
      *
      * @param scanResult scanResult that represents the BSSID