Merge "Volume dialog redesign"
diff --git a/Android.bp b/Android.bp
index e5d4b8b..defe655 100644
--- a/Android.bp
+++ b/Android.bp
@@ -324,6 +324,8 @@
         "core/java/android/view/IOnKeyguardExitResult.aidl",
         "core/java/android/view/IPinnedStackController.aidl",
         "core/java/android/view/IPinnedStackListener.aidl",
+        "core/java/android/view/IRemoteAnimationRunner.aidl",
+        "core/java/android/view/IRemoteAnimationFinishedCallback.aidl",
         "core/java/android/view/IRotationWatcher.aidl",
         "core/java/android/view/IWallpaperVisibilityListener.aidl",
         "core/java/android/view/IWindow.aidl",
@@ -471,8 +473,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 +492,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",
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 a75bbfa..c179ca8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5291,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;
@@ -5308,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 {
@@ -6355,7 +6367,7 @@
     method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName);
     method public void addUserRestriction(android.content.ComponentName, java.lang.String);
     method public boolean bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
-    method public boolean clearApplicationUserData(android.content.ComponentName, java.lang.String, android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener, java.util.concurrent.Executor);
+    method public boolean clearApplicationUserData(android.content.ComponentName, java.lang.String, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener);
     method public void clearCrossProfileIntentFilters(android.content.ComponentName);
     method public deprecated void clearDeviceOwnerApp(java.lang.String);
     method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
@@ -6391,6 +6403,7 @@
     method public int getLockTaskFeatures(android.content.ComponentName);
     method public java.lang.String[] getLockTaskPackages(android.content.ComponentName);
     method public java.lang.CharSequence getLongSupportMessage(android.content.ComponentName);
+    method public android.content.ComponentName getMandatoryBackupTransport();
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
     method public long getMaximumTimeToLock(android.content.ComponentName);
     method public int getOrganizationColor(android.content.ComponentName);
@@ -6490,6 +6503,7 @@
     method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException;
     method public void setLogoutEnabled(android.content.ComponentName, boolean);
     method public void setLongSupportMessage(android.content.ComponentName, java.lang.CharSequence);
+    method public void setMandatoryBackupTransport(android.content.ComponentName, android.content.ComponentName);
     method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
     method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
     method public void setMaximumTimeToLock(android.content.ComponentName, long);
@@ -6532,6 +6546,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);
@@ -6644,7 +6659,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
   }
@@ -20870,7 +20884,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();
@@ -20918,6 +20931,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);
@@ -20930,7 +20944,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);
@@ -26206,12 +26219,18 @@
   }
 
   public final class IpSecManager {
-    method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
-    method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
-    method public void applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+    method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
+    method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    method public void applyTransportModeTransform(java.net.Socket, int, android.net.IpSecTransform) throws java.io.IOException;
+    method public void applyTransportModeTransform(java.net.DatagramSocket, int, android.net.IpSecTransform) throws java.io.IOException;
+    method public void applyTransportModeTransform(java.io.FileDescriptor, int, android.net.IpSecTransform) throws java.io.IOException;
     method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
     method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
-    method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+    method public void removeTransportModeTransforms(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException;
+    method public void removeTransportModeTransforms(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException;
+    method public void removeTransportModeTransforms(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+    field public static final int DIRECTION_IN = 0; // 0x0
+    field public static final int DIRECTION_OUT = 1; // 0x1
   }
 
   public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
@@ -26234,18 +26253,15 @@
 
   public final class IpSecTransform implements java.lang.AutoCloseable {
     method public void close();
-    field public static final int DIRECTION_IN = 0; // 0x0
-    field public static final int DIRECTION_OUT = 1; // 0x1
   }
 
   public static class IpSecTransform.Builder {
     ctor public IpSecTransform.Builder(android.content.Context);
-    method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
-    method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(int, android.net.IpSecAlgorithm);
-    method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
-    method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform.Builder setAuthentication(android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform.Builder setEncryption(android.net.IpSecAlgorithm);
     method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
-    method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
   }
 
   public class LinkAddress implements android.os.Parcelable {
@@ -26328,10 +26344,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();
@@ -26341,7 +26357,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 {
@@ -32121,6 +32136,7 @@
   }
 
   public final class PowerManager {
+    method public int getLocationPowerSaveMode();
     method public boolean isDeviceIdleMode();
     method public boolean isIgnoringBatteryOptimizations(java.lang.String);
     method public boolean isInteractive();
@@ -32134,6 +32150,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
@@ -32421,17 +32441,18 @@
     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";
@@ -32444,6 +32465,7 @@
     field public static final java.lang.String DISALLOW_CONFIG_LOCALE = "no_config_locale";
     field public static final java.lang.String DISALLOW_CONFIG_LOCATION_MODE = "no_config_location_mode";
     field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
+    field public static final java.lang.String DISALLOW_CONFIG_SCREEN_TIMEOUT = "no_config_screen_timeout";
     field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
     field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
     field public static final java.lang.String DISALLOW_CONFIG_WIFI = "no_config_wifi";
@@ -32465,6 +32487,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";
@@ -41128,6 +41151,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
@@ -41139,6 +41163,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 {
@@ -42275,20 +42300,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();
@@ -42307,6 +42321,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);
@@ -45247,6 +45272,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
@@ -47867,7 +47893,6 @@
     field public static final int FIRST_APPLICATION_WINDOW = 1; // 0x1
     field public static final int FIRST_SUB_WINDOW = 1000; // 0x3e8
     field public static final int FIRST_SYSTEM_WINDOW = 2000; // 0x7d0
-    field public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 1L; // 0x1L
     field public static final int FLAGS_CHANGED = 4; // 0x4
     field public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 1; // 0x1
     field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000
@@ -47905,6 +47930,9 @@
     field public static final int LAST_SUB_WINDOW = 1999; // 0x7cf
     field public static final int LAST_SYSTEM_WINDOW = 2999; // 0xbb7
     field public static final int LAYOUT_CHANGED = 1; // 0x1
+    field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1; // 0x1
+    field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0; // 0x0
+    field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2; // 0x2
     field public static final int MEMORY_TYPE_CHANGED = 256; // 0x100
     field public static final deprecated int MEMORY_TYPE_GPU = 2; // 0x2
     field public static final deprecated int MEMORY_TYPE_HARDWARE = 1; // 0x1
@@ -47962,11 +47990,11 @@
     field public float buttonBrightness;
     field public float dimAmount;
     field public int flags;
-    field public long flags2;
     field public int format;
     field public int gravity;
     field public float horizontalMargin;
     field public float horizontalWeight;
+    field public int layoutInDisplayCutoutMode;
     field public deprecated int memoryType;
     field public java.lang.String packageName;
     field public int preferredDisplayModeId;
@@ -48014,6 +48042,8 @@
     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_APPEARED = 16; // 0x10
+    field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
     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
@@ -49240,14 +49270,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);
@@ -49255,15 +49284,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 {
@@ -49293,16 +49325,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 {
@@ -49311,21 +49349,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 {
@@ -49344,10 +49388,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 fbb8335..762b6e8 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";
@@ -162,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";
@@ -191,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 {
@@ -318,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 {
@@ -1094,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);
   }
 
 }
@@ -1703,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);
@@ -1726,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
@@ -1737,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
@@ -1955,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();
@@ -4388,6 +4454,8 @@
     method public deprecated void setDataEnabled(int, boolean);
     method public boolean setRadio(boolean);
     method public boolean setRadioPower(boolean);
+    method public void setSimPowerState(int);
+    method public void setSimPowerStateForSlot(int, int);
     method public deprecated void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
     method public void setVoiceActivationState(int);
     method public deprecated void silenceRinger();
@@ -4397,8 +4465,6 @@
     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
@@ -4422,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();
@@ -4469,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 {
@@ -4592,9 +4647,12 @@
   }
 
   public final class StatsManager {
+    method public boolean addConfiguration(java.lang.String, byte[], java.lang.String, java.lang.String);
     method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String);
+    method public byte[] getData(java.lang.String);
     method public byte[] getData(long);
     method public byte[] getMetadata();
+    method public boolean removeConfiguration(java.lang.String);
     method public boolean removeConfiguration(long);
   }
 
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..2c35d41 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) \
@@ -198,6 +199,24 @@
 
 include $(BUILD_NATIVE_TEST)
 
+##############################
+# stats proto static java lib
+##############################
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := statsdprotolite
+
+LOCAL_SRC_FILES := \
+    src/stats_log.proto \
+    src/statsd_config.proto \
+    src/atoms.proto
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    platformprotoslite
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
 
 statsd_common_src:=
 statsd_common_aidl_includes:=
@@ -208,4 +227,4 @@
 
 ##############################
 
-include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
+include $(call all-makefiles-under,$(LOCAL_PATH))
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/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 437f9a8..c260ae1 100644
--- a/cmds/statsd/statsd.rc
+++ b/cmds/statsd/statsd.rc
@@ -16,3 +16,8 @@
     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/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/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index e61c5b7..4bcd677 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -16,12 +16,14 @@
 
 package android.app;
 
+import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
 import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.Context;
@@ -44,6 +46,7 @@
 import android.util.Slog;
 import android.view.AppTransitionAnimationSpec;
 import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.RemoteAnimationAdapter;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
@@ -241,6 +244,8 @@
     private static final String KEY_INSTANT_APP_VERIFICATION_BUNDLE
             = "android:instantapps.installerbundle";
     private static final String KEY_SPECS_FUTURE = "android:activity.specsFuture";
+    private static final String KEY_REMOTE_ANIMATION_ADAPTER
+            = "android:activity.remoteAnimationAdapter";
 
     /** @hide */
     public static final int ANIM_NONE = 0;
@@ -268,6 +273,8 @@
     public static final int ANIM_CLIP_REVEAL = 11;
     /** @hide */
     public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12;
+    /** @hide */
+    public static final int ANIM_REMOTE_ANIMATION = 13;
 
     private String mPackageName;
     private Rect mLaunchBounds;
@@ -304,6 +311,7 @@
     private int mRotationAnimationHint = -1;
     private Bundle mAppVerificationBundle;
     private IAppTransitionAnimationSpecsFuture mSpecsFuture;
+    private RemoteAnimationAdapter mRemoteAnimationAdapter;
 
     /**
      * Create an ActivityOptions specifying a custom animation to run when
@@ -826,6 +834,20 @@
         return opts;
     }
 
+    /**
+     * Create an {@link ActivityOptions} instance that lets the application control the entire
+     * animation using a {@link RemoteAnimationAdapter}.
+     * @hide
+     */
+    @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+    public static ActivityOptions makeRemoteAnimation(
+            RemoteAnimationAdapter remoteAnimationAdapter) {
+        final ActivityOptions opts = new ActivityOptions();
+        opts.mRemoteAnimationAdapter = remoteAnimationAdapter;
+        opts.mAnimationType = ANIM_REMOTE_ANIMATION;
+        return opts;
+    }
+
     /** @hide */
     public boolean getLaunchTaskBehind() {
         return mAnimationType == ANIM_LAUNCH_TASK_BEHIND;
@@ -922,6 +944,7 @@
             mSpecsFuture = IAppTransitionAnimationSpecsFuture.Stub.asInterface(opts.getBinder(
                     KEY_SPECS_FUTURE));
         }
+        mRemoteAnimationAdapter = opts.getParcelable(KEY_REMOTE_ANIMATION_ADAPTER);
     }
 
     /**
@@ -1070,6 +1093,11 @@
     }
 
     /** @hide */
+    public RemoteAnimationAdapter getRemoteAnimationAdapter() {
+        return mRemoteAnimationAdapter;
+    }
+
+    /** @hide */
     public static ActivityOptions fromBundle(Bundle bOptions) {
         return bOptions != null ? new ActivityOptions(bOptions) : null;
     }
@@ -1309,6 +1337,7 @@
         mAnimSpecs = otherOptions.mAnimSpecs;
         mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
         mSpecsFuture = otherOptions.mSpecsFuture;
+        mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter;
     }
 
     /**
@@ -1403,7 +1432,9 @@
         if (mAppVerificationBundle != null) {
             b.putBundle(KEY_INSTANT_APP_VERIFICATION_BUNDLE, mAppVerificationBundle);
         }
-
+        if (mRemoteAnimationAdapter != null) {
+            b.putParcelable(KEY_REMOTE_ANIMATION_ADAPTER, mRemoteAnimationAdapter);
+        }
         return b;
     }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index de346f3..70c383b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1752,9 +1752,11 @@
                     handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
                             (IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
                     break;
-                case ATTACH_AGENT:
-                    handleAttachAgent((String) msg.obj);
+                case ATTACH_AGENT: {
+                    Application app = getApplication();
+                    handleAttachAgent((String) msg.obj, app != null ? app.mLoadedApk : null);
                     break;
+                }
                 case APPLICATION_INFO_CHANGED:
                     mUpdatingSystemConfig = true;
                     try {
@@ -1768,7 +1770,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;
@@ -3239,11 +3248,23 @@
         }
     }
 
-    static final void handleAttachAgent(String agent) {
+    private static boolean attemptAttachAgent(String agent, ClassLoader classLoader) {
         try {
-            VMDebug.attachAgent(agent);
+            VMDebug.attachAgent(agent, classLoader);
+            return true;
         } catch (IOException e) {
-            Slog.e(TAG, "Attaching agent failed: " + agent);
+            Slog.e(TAG, "Attaching agent with " + classLoader + " failed: " + agent);
+            return false;
+        }
+    }
+
+    static void handleAttachAgent(String agent, LoadedApk loadedApk) {
+        ClassLoader classLoader = loadedApk != null ? loadedApk.getClassLoader() : null;
+        if (attemptAttachAgent(agent, classLoader)) {
+            return;
+        }
+        if (classLoader != null) {
+            attemptAttachAgent(agent, null);
         }
     }
 
@@ -5535,12 +5556,16 @@
         mCompatConfiguration = new Configuration(data.config);
 
         mProfiler = new Profiler();
+        String agent = null;
         if (data.initProfilerInfo != null) {
             mProfiler.profileFile = data.initProfilerInfo.profileFile;
             mProfiler.profileFd = data.initProfilerInfo.profileFd;
             mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
             mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
             mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput;
+            if (data.initProfilerInfo.attachAgentDuringBind) {
+                agent = data.initProfilerInfo.agent;
+            }
         }
 
         // send up app name; do this *before* waiting for debugger
@@ -5590,6 +5615,10 @@
 
         data.loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo);
 
+        if (agent != null) {
+            handleAttachAgent(agent, data.loadedApk);
+        }
+
         /**
          * Switch this process to density compatibility mode if needed.
          */
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/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java
index d523427..a295c4c 100644
--- a/core/java/android/app/ProfilerInfo.java
+++ b/core/java/android/app/ProfilerInfo.java
@@ -55,14 +55,24 @@
      */
     public final String agent;
 
+    /**
+     * Whether the {@link agent} should be attached early (before bind-application) or during
+     * bind-application. Agents attached prior to binding cannot be loaded from the app's APK
+     * directly and must be given as an absolute path (or available in the default LD_LIBRARY_PATH).
+     * Agents attached during bind-application will miss early setup (e.g., resource initialization
+     * and classloader generation), but are searched in the app's library search path.
+     */
+    public final boolean attachAgentDuringBind;
+
     public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop,
-            boolean streaming, String agent) {
+            boolean streaming, String agent, boolean attachAgentDuringBind) {
         profileFile = filename;
         profileFd = fd;
         samplingInterval = interval;
         autoStopProfiler = autoStop;
         streamingOutput = streaming;
         this.agent = agent;
+        this.attachAgentDuringBind = attachAgentDuringBind;
     }
 
     public ProfilerInfo(ProfilerInfo in) {
@@ -72,6 +82,7 @@
         autoStopProfiler = in.autoStopProfiler;
         streamingOutput = in.streamingOutput;
         agent = in.agent;
+        attachAgentDuringBind = in.attachAgentDuringBind;
     }
 
     /**
@@ -110,6 +121,7 @@
         out.writeInt(autoStopProfiler ? 1 : 0);
         out.writeInt(streamingOutput ? 1 : 0);
         out.writeString(agent);
+        out.writeBoolean(attachAgentDuringBind);
     }
 
     public static final Parcelable.Creator<ProfilerInfo> CREATOR =
@@ -132,6 +144,7 @@
         autoStopProfiler = in.readInt() != 0;
         streamingOutput = in.readInt() != 0;
         agent = in.readString();
+        attachAgentDuringBind = in.readBoolean();
     }
 
     @Override
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..0455949 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -18,7 +18,6 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
-import android.annotation.Condemned;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -50,8 +49,6 @@
 import android.net.ProxyInfo;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -1254,6 +1251,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 +6074,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 +6503,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 +6515,6 @@
             SKIP_SETUP_WIZARD,
             MAKE_USER_EPHEMERAL,
             MAKE_USER_DEMO,
-            START_USER_IN_BACKGROUND,
             LEAVE_ALL_SYSTEM_APPS_ENABLED
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -6526,7 +6543,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 +6563,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 +6581,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 +6600,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 +6637,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 +6655,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");
@@ -8593,6 +8630,13 @@
      *
      * <p> Backup service is off by default when device owner is present.
      *
+     * <p> If backups are made mandatory by specifying a non-null mandatory backup transport using
+     * the {@link DevicePolicyManager#setMandatoryBackupTransport} method, the backup service is
+     * automatically enabled.
+     *
+     * <p> If the backup service is disabled using this method after the mandatory backup transport
+     * has been set, the mandatory backup transport is cleared.
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param enabled {@code true} to enable the backup service, {@code false} to disable it.
      * @throws SecurityException if {@code admin} is not a device owner.
@@ -8624,6 +8668,43 @@
     }
 
     /**
+     * Makes backups mandatory and enforces the usage of the specified backup transport.
+     *
+     * <p>When a {@code null} backup transport is specified, backups are made optional again.
+     * <p>Only device owner can call this method.
+     * <p>If backups were disabled and a non-null backup transport {@link ComponentName} is
+     * specified, backups will be enabled.
+     *
+     * @param admin admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param backupTransportComponent The backup transport layer to be used for mandatory backups.
+     * @throws SecurityException if {@code admin} is not a device owner.
+     */
+    public void setMandatoryBackupTransport(
+            @NonNull ComponentName admin, @Nullable ComponentName backupTransportComponent) {
+        try {
+            mService.setMandatoryBackupTransport(admin, backupTransportComponent);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the backup transport which has to be used for backups if backups are mandatory or
+     * {@code null} if backups are not mandatory.
+     *
+     * @return a {@link ComponentName} of the backup transport layer to be used if backups are
+     *         mandatory or {@code null} if backups are not mandatory.
+     */
+    public ComponentName getMandatoryBackupTransport() {
+        try {
+            return mService.getMandatoryBackupTransport();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
      * Called by a device owner to control the network logging feature.
      *
      * <p> Network logs contain DNS lookup and connect() library call events. The following library
@@ -8899,15 +8980,6 @@
         }
     }
 
-    /** {@hide} */
-    @Condemned
-    @Deprecated
-    public boolean clearApplicationUserData(@NonNull ComponentName admin,
-            @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener,
-            @NonNull Handler handler) {
-        return clearApplicationUserData(admin, packageName, listener, new HandlerExecutor(handler));
-    }
-
     /**
      * Called by the device owner or profile owner to clear application user data of a given
      * package. The behaviour of this is equivalent to the target application calling
@@ -8918,14 +8990,14 @@
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param packageName The name of the package which will have its user data wiped.
-     * @param listener A callback object that will inform the caller when the clearing is done.
      * @param executor The executor through which the listener should be invoked.
+     * @param listener A callback object that will inform the caller when the clearing is done.
      * @throws SecurityException if the caller is not the device owner/profile owner.
      * @return whether the clearing succeeded.
      */
     public boolean clearApplicationUserData(@NonNull ComponentName admin,
-            @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener,
-            @NonNull @CallbackExecutor Executor executor) {
+            @NonNull String packageName, @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnClearApplicationUserDataListener listener) {
         throwIfParentInstance("clearAppData");
         Preconditions.checkNotNull(executor);
         try {
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index b692ffd9..531bef0 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -123,4 +123,13 @@
      * @param userId User ID of the profile.
      */
     public abstract void reportSeparateProfileChallengeChanged(@UserIdInt int userId);
+
+    /**
+     * Check whether the user could have their password reset in an untrusted manor due to there
+     * being an admin which can call {@link #resetPassword} to reset the password without knowledge
+     * of the previous password.
+     *
+     * @param userId The user in question
+     */
+    public abstract boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId);
 }
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..9cdd1f8 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);
@@ -358,6 +359,8 @@
 
     void setBackupServiceEnabled(in ComponentName admin, boolean enabled);
     boolean isBackupServiceEnabled(in ComponentName admin);
+    void setMandatoryBackupTransport(in ComponentName admin, in ComponentName backupTransportComponent);
+    ComponentName getMandatoryBackupTransport();
 
     void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled);
     boolean isNetworkLoggingEnabled(in ComponentName admin);
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/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 792cb5f..f3ca746 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -294,7 +294,8 @@
      *
      * @param transport ComponentName of the service hosting the transport. This is different from
      *                  the transport's name that is returned by {@link BackupTransport#name()}.
-     * @param listener A listener object to get a callback on the transport being selected.
+     * @param listener A listener object to get a callback on the transport being selected. It may
+     *                 be {@code null}.
      *
      * @hide
      */
diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java
index 5053dc6..c71bf2e 100644
--- a/core/java/android/app/job/JobParameters.java
+++ b/core/java/android/app/job/JobParameters.java
@@ -70,6 +70,7 @@
     private final Network network;
 
     private int stopReason; // Default value of stopReason is REASON_CANCELED
+    private String debugStopReason; // Human readable stop reason for debugging.
 
     /** @hide */
     public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
@@ -104,6 +105,14 @@
     }
 
     /**
+     * Reason onStopJob() was called on this job.
+     * @hide
+     */
+    public String getDebugStopReason() {
+        return debugStopReason;
+    }
+
+    /**
      * @return The extras you passed in when constructing this job with
      * {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will
      * never be null. If you did not set any extras this will be an empty bundle.
@@ -288,11 +297,13 @@
             network = null;
         }
         stopReason = in.readInt();
+        debugStopReason = in.readString();
     }
 
     /** @hide */
-    public void setStopReason(int reason) {
+    public void setStopReason(int reason, String debugStopReason) {
         stopReason = reason;
+        this.debugStopReason = debugStopReason;
     }
 
     @Override
@@ -323,6 +334,7 @@
             dest.writeInt(0);
         }
         dest.writeInt(stopReason);
+        dest.writeString(debugStopReason);
     }
 
     public static final Creator<JobParameters> CREATOR = new Creator<JobParameters>() {
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/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index a18f22e..6d6c02a 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6285,6 +6285,31 @@
                 + " " + packageName + "}";
         }
 
+        public String dumpState_temp() {
+            String flags = "";
+            flags += ((applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ? "U" : "");
+            flags += ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? "S" : "");
+            if ("".equals(flags)) {
+                flags = "-";
+            }
+            String privFlags = "";
+            privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0 ? "P" : "");
+            privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0 ? "O" : "");
+            privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0 ? "V" : "");
+            if ("".equals(privFlags)) {
+                privFlags = "-";
+            }
+            return "Package{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + packageName
+            + ", ver:" + getLongVersionCode()
+            + ", path: " + codePath
+            + ", flags: " + flags
+            + ", privFlags: " + privFlags
+            + ", extra: " + (mExtras == null ? "<<NULL>>" : Integer.toHexString(System.identityHashCode(mExtras)) + "}")
+            + "}";
+        }
+
         @Override
         public int describeContents() {
             return 0;
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/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 3f6dd2e..078958a 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -179,6 +179,11 @@
     public abstract void persistBrightnessSliderEvents();
 
     /**
+     * Notifies the display manager that resource overlays have changed.
+     */
+    public abstract void onOverlayChanged();
+
+    /**
      * Describes the requested power state of the display.
      *
      * This object is intended to describe the general characteristics of the
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%
rename from telephony/java/android/telephony/data/InterfaceAddress.aidl
rename 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/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl
index d9b57db..3fe531f 100644
--- a/core/java/android/net/IIpSecService.aidl
+++ b/core/java/android/net/IIpSecService.aidl
@@ -31,7 +31,7 @@
 interface IIpSecService
 {
     IpSecSpiResponse allocateSecurityParameterIndex(
-            int direction, in String remoteAddress, int requestedSpi, in IBinder binder);
+            in String destinationAddress, int requestedSpi, in IBinder binder);
 
     void releaseSecurityParameterIndex(int resourceId);
 
@@ -43,7 +43,7 @@
 
     void deleteTransportModeTransform(int transformId);
 
-    void applyTransportModeTransform(in ParcelFileDescriptor socket, int transformId);
+    void applyTransportModeTransform(in ParcelFileDescriptor socket, int direction, int transformId);
 
-    void removeTransportModeTransform(in ParcelFileDescriptor socket, int transformId);
+    void removeTransportModeTransforms(in ParcelFileDescriptor socket, int transformId);
 }
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index 7d752e8..c69a4d4 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -256,13 +256,19 @@
         return getName().equals(AUTH_CRYPT_AES_GCM);
     }
 
+    // Because encryption keys are sensitive and userdebug builds are used by large user pools
+    // such as beta testers, we only allow sensitive info such as keys on eng builds.
+    private static boolean isUnsafeBuild() {
+        return Build.IS_DEBUGGABLE && Build.IS_ENG;
+    }
+
     @Override
     public String toString() {
         return new StringBuilder()
                 .append("{mName=")
                 .append(mName)
                 .append(", mKey=")
-                .append(Build.IS_DEBUGGABLE ? HexDump.toHexString(mKey) : "<hidden>")
+                .append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "<hidden>")
                 .append(", mTruncLenBits=")
                 .append(mTruncLenBits)
                 .append("}")
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index f54ceb5..80b0af3 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -32,59 +32,29 @@
     // MODE_TRANSPORT or MODE_TUNNEL
     private int mMode = IpSecTransform.MODE_TRANSPORT;
 
-    // Needs to be valid only for tunnel mode
     // Preventing this from being null simplifies Java->Native binder
-    private String mLocalAddress = "";
+    private String mSourceAddress = "";
 
     // Preventing this from being null simplifies Java->Native binder
-    private String mRemoteAddress = "";
+    private String mDestinationAddress = "";
 
     // The underlying Network that represents the "gateway" Network
     // for outbound packets. It may also be used to select packets.
     private Network mNetwork;
 
-    /**
-     * This class captures the parameters that specifically apply to inbound or outbound traffic.
-     */
-    public static class Flow {
-        // Minimum requirements for identifying a transform
-        // SPI identifying the IPsec flow in packet processing
-        // and a remote IP address
-        private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID;
+    // Minimum requirements for identifying a transform
+    // SPI identifying the IPsec SA in packet processing
+    // and a destination IP address
+    private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID;
 
-        // Encryption Algorithm
-        private IpSecAlgorithm mEncryption;
+    // Encryption Algorithm
+    private IpSecAlgorithm mEncryption;
 
-        // Authentication Algorithm
-        private IpSecAlgorithm mAuthentication;
+    // Authentication Algorithm
+    private IpSecAlgorithm mAuthentication;
 
-        // Authenticated Encryption Algorithm
-        private IpSecAlgorithm mAuthenticatedEncryption;
-
-        @Override
-        public String toString() {
-            return new StringBuilder()
-                    .append("{mSpiResourceId=")
-                    .append(mSpiResourceId)
-                    .append(", mEncryption=")
-                    .append(mEncryption)
-                    .append(", mAuthentication=")
-                    .append(mAuthentication)
-                    .append(", mAuthenticatedEncryption=")
-                    .append(mAuthenticatedEncryption)
-                    .append("}")
-                    .toString();
-        }
-
-        static boolean equals(IpSecConfig.Flow lhs, IpSecConfig.Flow rhs) {
-            if (lhs == null || rhs == null) return (lhs == rhs);
-            return (lhs.mSpiResourceId == rhs.mSpiResourceId
-                    && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption)
-                    && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication));
-        }
-    }
-
-    private final Flow[] mFlow = new Flow[] {new Flow(), new Flow()};
+    // Authenticated Encryption Algorithm
+    private IpSecAlgorithm mAuthenticatedEncryption;
 
     // For tunnel mode IPv4 UDP Encapsulation
     // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
@@ -100,36 +70,37 @@
         mMode = mode;
     }
 
-    /** Set the local IP address for Tunnel mode */
-    public void setLocalAddress(String localAddress) {
-        mLocalAddress = localAddress;
+    /** Set the source IP addres for this IPsec transform */
+    public void setSourceAddress(String sourceAddress) {
+        mSourceAddress = sourceAddress;
     }
 
-    /** Set the remote IP address for this IPsec transform */
-    public void setRemoteAddress(String remoteAddress) {
-        mRemoteAddress = remoteAddress;
+    /** Set the destination IP address for this IPsec transform */
+    public void setDestinationAddress(String destinationAddress) {
+        mDestinationAddress = destinationAddress;
     }
 
-    /** Set the SPI for a given direction by resource ID */
-    public void setSpiResourceId(int direction, int resourceId) {
-        mFlow[direction].mSpiResourceId = resourceId;
+    /** Set the SPI by resource ID */
+    public void setSpiResourceId(int resourceId) {
+        mSpiResourceId = resourceId;
     }
 
-    /** Set the encryption algorithm for a given direction */
-    public void setEncryption(int direction, IpSecAlgorithm encryption) {
-        mFlow[direction].mEncryption = encryption;
+    /** Set the encryption algorithm */
+    public void setEncryption(IpSecAlgorithm encryption) {
+        mEncryption = encryption;
     }
 
-    /** Set the authentication algorithm for a given direction */
-    public void setAuthentication(int direction, IpSecAlgorithm authentication) {
-        mFlow[direction].mAuthentication = authentication;
+    /** Set the authentication algorithm */
+    public void setAuthentication(IpSecAlgorithm authentication) {
+        mAuthentication = authentication;
     }
 
-    /** Set the authenticated encryption algorithm for a given direction */
-    public void setAuthenticatedEncryption(int direction, IpSecAlgorithm authenticatedEncryption) {
-        mFlow[direction].mAuthenticatedEncryption = authenticatedEncryption;
+    /** Set the authenticated encryption algorithm */
+    public void setAuthenticatedEncryption(IpSecAlgorithm authenticatedEncryption) {
+        mAuthenticatedEncryption = authenticatedEncryption;
     }
 
+    /** Set the underlying network that will carry traffic for this transform */
     public void setNetwork(Network network) {
         mNetwork = network;
     }
@@ -155,28 +126,28 @@
         return mMode;
     }
 
-    public String getLocalAddress() {
-        return mLocalAddress;
+    public String getSourceAddress() {
+        return mSourceAddress;
     }
 
-    public int getSpiResourceId(int direction) {
-        return mFlow[direction].mSpiResourceId;
+    public int getSpiResourceId() {
+        return mSpiResourceId;
     }
 
-    public String getRemoteAddress() {
-        return mRemoteAddress;
+    public String getDestinationAddress() {
+        return mDestinationAddress;
     }
 
-    public IpSecAlgorithm getEncryption(int direction) {
-        return mFlow[direction].mEncryption;
+    public IpSecAlgorithm getEncryption() {
+        return mEncryption;
     }
 
-    public IpSecAlgorithm getAuthentication(int direction) {
-        return mFlow[direction].mAuthentication;
+    public IpSecAlgorithm getAuthentication() {
+        return mAuthentication;
     }
 
-    public IpSecAlgorithm getAuthenticatedEncryption(int direction) {
-        return mFlow[direction].mAuthenticatedEncryption;
+    public IpSecAlgorithm getAuthenticatedEncryption() {
+        return mAuthenticatedEncryption;
     }
 
     public Network getNetwork() {
@@ -209,17 +180,13 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mMode);
-        out.writeString(mLocalAddress);
-        out.writeString(mRemoteAddress);
+        out.writeString(mSourceAddress);
+        out.writeString(mDestinationAddress);
         out.writeParcelable(mNetwork, flags);
-        out.writeInt(mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId);
-        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mEncryption, flags);
-        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthentication, flags);
-        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption, flags);
-        out.writeInt(mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId);
-        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mEncryption, flags);
-        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication, flags);
-        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption, flags);
+        out.writeInt(mSpiResourceId);
+        out.writeParcelable(mEncryption, flags);
+        out.writeParcelable(mAuthentication, flags);
+        out.writeParcelable(mAuthenticatedEncryption, flags);
         out.writeInt(mEncapType);
         out.writeInt(mEncapSocketResourceId);
         out.writeInt(mEncapRemotePort);
@@ -231,22 +198,15 @@
 
     private IpSecConfig(Parcel in) {
         mMode = in.readInt();
-        mLocalAddress = in.readString();
-        mRemoteAddress = in.readString();
+        mSourceAddress = in.readString();
+        mDestinationAddress = in.readString();
         mNetwork = (Network) in.readParcelable(Network.class.getClassLoader());
-        mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId = in.readInt();
-        mFlow[IpSecTransform.DIRECTION_IN].mEncryption =
+        mSpiResourceId = in.readInt();
+        mEncryption =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        mFlow[IpSecTransform.DIRECTION_IN].mAuthentication =
+        mAuthentication =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId = in.readInt();
-        mFlow[IpSecTransform.DIRECTION_OUT].mEncryption =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption =
+        mAuthenticatedEncryption =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         mEncapType = in.readInt();
         mEncapSocketResourceId = in.readInt();
@@ -260,10 +220,10 @@
         strBuilder
                 .append("{mMode=")
                 .append(mMode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT")
-                .append(", mLocalAddress=")
-                .append(mLocalAddress)
-                .append(", mRemoteAddress=")
-                .append(mRemoteAddress)
+                .append(", mSourceAddress=")
+                .append(mSourceAddress)
+                .append(", mDestinationAddress=")
+                .append(mDestinationAddress)
                 .append(", mNetwork=")
                 .append(mNetwork)
                 .append(", mEncapType=")
@@ -274,10 +234,14 @@
                 .append(mEncapRemotePort)
                 .append(", mNattKeepaliveInterval=")
                 .append(mNattKeepaliveInterval)
-                .append(", mFlow[OUT]=")
-                .append(mFlow[IpSecTransform.DIRECTION_OUT])
-                .append(", mFlow[IN]=")
-                .append(mFlow[IpSecTransform.DIRECTION_IN])
+                .append("{mSpiResourceId=")
+                .append(mSpiResourceId)
+                .append(", mEncryption=")
+                .append(mEncryption)
+                .append(", mAuthentication=")
+                .append(mAuthentication)
+                .append(", mAuthenticatedEncryption=")
+                .append(mAuthenticatedEncryption)
                 .append("}");
 
         return strBuilder.toString();
@@ -299,17 +263,18 @@
     public static boolean equals(IpSecConfig lhs, IpSecConfig rhs) {
         if (lhs == null || rhs == null) return (lhs == rhs);
         return (lhs.mMode == rhs.mMode
-                && lhs.mLocalAddress.equals(rhs.mLocalAddress)
-                && lhs.mRemoteAddress.equals(rhs.mRemoteAddress)
+                && lhs.mSourceAddress.equals(rhs.mSourceAddress)
+                && lhs.mDestinationAddress.equals(rhs.mDestinationAddress)
                 && ((lhs.mNetwork != null && lhs.mNetwork.equals(rhs.mNetwork))
                         || (lhs.mNetwork == rhs.mNetwork))
                 && lhs.mEncapType == rhs.mEncapType
                 && lhs.mEncapSocketResourceId == rhs.mEncapSocketResourceId
                 && lhs.mEncapRemotePort == rhs.mEncapRemotePort
                 && lhs.mNattKeepaliveInterval == rhs.mNattKeepaliveInterval
-                && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_OUT],
-                        rhs.mFlow[IpSecTransform.DIRECTION_OUT])
-                && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_IN],
-                        rhs.mFlow[IpSecTransform.DIRECTION_IN]));
+                && lhs.mSpiResourceId == rhs.mSpiResourceId
+                && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption)
+                && IpSecAlgorithm.equals(
+                        lhs.mAuthenticatedEncryption, rhs.mAuthenticatedEncryption)
+                && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication));
     }
 }
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 34cfa9b..2202df3 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -17,6 +17,7 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -33,6 +34,8 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.DatagramSocket;
 import java.net.InetAddress;
 import java.net.Socket;
@@ -53,6 +56,23 @@
     private static final String TAG = "IpSecManager";
 
     /**
+     * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
+     * applies to traffic towards the host.
+     */
+    public static final int DIRECTION_IN = 0;
+
+    /**
+     * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
+     * applies to traffic from the host.
+     */
+    public static final int DIRECTION_OUT = 1;
+
+    /** @hide */
+    @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PolicyDirection {}
+
+    /**
      * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
      *
      * <p>No IPsec packet may contain an SPI of 0.
@@ -125,7 +145,7 @@
      */
     public static final class SecurityParameterIndex implements AutoCloseable {
         private final IIpSecService mService;
-        private final InetAddress mRemoteAddress;
+        private final InetAddress mDestinationAddress;
         private final CloseGuard mCloseGuard = CloseGuard.get();
         private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
         private int mResourceId = INVALID_RESOURCE_ID;
@@ -164,14 +184,14 @@
         }
 
         private SecurityParameterIndex(
-                @NonNull IIpSecService service, int direction, InetAddress remoteAddress, int spi)
+                @NonNull IIpSecService service, InetAddress destinationAddress, int spi)
                 throws ResourceUnavailableException, SpiUnavailableException {
             mService = service;
-            mRemoteAddress = remoteAddress;
+            mDestinationAddress = destinationAddress;
             try {
                 IpSecSpiResponse result =
                         mService.allocateSecurityParameterIndex(
-                                direction, remoteAddress.getHostAddress(), spi, new Binder());
+                                destinationAddress.getHostAddress(), spi, new Binder());
 
                 if (result == null) {
                     throw new NullPointerException("Received null response from IpSecService");
@@ -216,25 +236,23 @@
     }
 
     /**
-     * Reserve a random SPI for traffic bound to or from the specified remote address.
+     * Reserve a random SPI for traffic bound to or from the specified destination address.
      *
      * <p>If successful, this SPI is guaranteed available until released by a call to {@link
      * SecurityParameterIndex#close()}.
      *
-     * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
-     * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress
+     * @param destinationAddress the destination address for traffic bearing the requested SPI.
+     *     For inbound traffic, the destination should be an address currently assigned on-device.
      * @return the reserved SecurityParameterIndex
-     * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
-     *     for this user
-     * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved
+     * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are
+     *     currently allocated for this user
      */
-    public SecurityParameterIndex allocateSecurityParameterIndex(
-            int direction, InetAddress remoteAddress) throws ResourceUnavailableException {
+    public SecurityParameterIndex allocateSecurityParameterIndex(InetAddress destinationAddress)
+            throws ResourceUnavailableException {
         try {
             return new SecurityParameterIndex(
                     mService,
-                    direction,
-                    remoteAddress,
+                    destinationAddress,
                     IpSecManager.INVALID_SECURITY_PARAMETER_INDEX);
         } catch (SpiUnavailableException unlikely) {
             throw new ResourceUnavailableException("No SPIs available");
@@ -242,26 +260,27 @@
     }
 
     /**
-     * Reserve the requested SPI for traffic bound to or from the specified remote address.
+     * Reserve the requested SPI for traffic bound to or from the specified destination address.
      *
      * <p>If successful, this SPI is guaranteed available until released by a call to {@link
      * SecurityParameterIndex#close()}.
      *
-     * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
-     * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress
+     * @param destinationAddress the destination address for traffic bearing the requested SPI.
+     *     For inbound traffic, the destination should be an address currently assigned on-device.
      * @param requestedSpi the requested SPI, or '0' to allocate a random SPI
      * @return the reserved SecurityParameterIndex
-     * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
-     *     for this user
-     * @throws SpiUnavailableException indicating that the requested SPI could not be reserved
+     * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are
+     *     currently allocated for this user
+     * @throws {@link #SpiUnavailableException} indicating that the requested SPI could not be
+     *     reserved
      */
     public SecurityParameterIndex allocateSecurityParameterIndex(
-            int direction, InetAddress remoteAddress, int requestedSpi)
+            InetAddress destinationAddress, int requestedSpi)
             throws SpiUnavailableException, ResourceUnavailableException {
         if (requestedSpi == IpSecManager.INVALID_SECURITY_PARAMETER_INDEX) {
             throw new IllegalArgumentException("Requested SPI must be a valid (non-zero) SPI");
         }
-        return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi);
+        return new SecurityParameterIndex(mService, destinationAddress, requestedSpi);
     }
 
     /**
@@ -269,14 +288,14 @@
      *
      * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
      * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
-     * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+     * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
      * unprotected traffic can resume on that socket.
      *
      * <p>For security reasons, the destination address of any traffic on the socket must match the
      * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
      * other IP address will result in an IOException. In addition, reads and writes on the socket
      * will throw IOException if the user deactivates the transform (by calling {@link
-     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
      *
      * <h4>Rekey Procedure</h4>
      *
@@ -287,15 +306,14 @@
      * in-flight packets have been received.
      *
      * @param socket a stream socket
+     * @param direction the policy direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
      * @param transform a transport mode {@code IpSecTransform}
      * @throws IOException indicating that the transform could not be applied
-     * @hide
      */
-    public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
+    public void applyTransportModeTransform(
+            Socket socket, int direction, IpSecTransform transform)
             throws IOException {
-        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) {
-            applyTransportModeTransform(pfd, transform);
-        }
+        applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform);
     }
 
     /**
@@ -303,14 +321,14 @@
      *
      * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
      * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
-     * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+     * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
      * unprotected traffic can resume on that socket.
      *
      * <p>For security reasons, the destination address of any traffic on the socket must match the
      * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
      * other IP address will result in an IOException. In addition, reads and writes on the socket
      * will throw IOException if the user deactivates the transform (by calling {@link
-     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
      *
      * <h4>Rekey Procedure</h4>
      *
@@ -321,15 +339,13 @@
      * in-flight packets have been received.
      *
      * @param socket a datagram socket
+     * @param direction the policy direction either DIRECTION_IN or DIRECTION_OUT
      * @param transform a transport mode {@code IpSecTransform}
      * @throws IOException indicating that the transform could not be applied
-     * @hide
      */
-    public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
-            throws IOException {
-        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) {
-            applyTransportModeTransform(pfd, transform);
-        }
+    public void applyTransportModeTransform(
+            DatagramSocket socket, int direction, IpSecTransform transform) throws IOException {
+        applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform);
     }
 
     /**
@@ -337,14 +353,14 @@
      *
      * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
      * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
-     * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+     * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
      * unprotected traffic can resume on that socket.
      *
      * <p>For security reasons, the destination address of any traffic on the socket must match the
      * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
      * other IP address will result in an IOException. In addition, reads and writes on the socket
      * will throw IOException if the user deactivates the transform (by calling {@link
-     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
      *
      * <h4>Rekey Procedure</h4>
      *
@@ -355,24 +371,17 @@
      * in-flight packets have been received.
      *
      * @param socket a socket file descriptor
+     * @param direction the policy direction either DIRECTION_IN or DIRECTION_OUT
      * @param transform a transport mode {@code IpSecTransform}
      * @throws IOException indicating that the transform could not be applied
      */
-    public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
+    public void applyTransportModeTransform(
+            FileDescriptor socket, int direction, IpSecTransform transform)
             throws IOException {
         // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor()
-        // constructor takes control and closes the user's FD when we exit the method
-        // This is behaviorally the same as the other versions, but the PFD constructor does not
-        // dup() automatically, whereas PFD.fromSocket() and PDF.fromDatagramSocket() do dup().
+        // constructor takes control and closes the user's FD when we exit the method.
         try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
-            applyTransportModeTransform(pfd, transform);
-        }
-    }
-
-    /* Call down to activate a transform */
-    private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
-        try {
-            mService.applyTransportModeTransform(pfd, transform.getResourceId());
+            mService.applyTransportModeTransform(pfd, direction, transform.getResourceId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -407,13 +416,10 @@
      * @param socket a socket that previously had a transform applied to it
      * @param transform the IPsec Transform that was previously applied to the given socket
      * @throws IOException indicating that the transform could not be removed from the socket
-     * @hide
      */
-    public void removeTransportModeTransform(Socket socket, IpSecTransform transform)
+    public void removeTransportModeTransforms(Socket socket, IpSecTransform transform)
             throws IOException {
-        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) {
-            removeTransportModeTransform(pfd, transform);
-        }
+        removeTransportModeTransforms(socket.getFileDescriptor$(), transform);
     }
 
     /**
@@ -430,13 +436,10 @@
      * @param socket a socket that previously had a transform applied to it
      * @param transform the IPsec Transform that was previously applied to the given socket
      * @throws IOException indicating that the transform could not be removed from the socket
-     * @hide
      */
-    public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
+    public void removeTransportModeTransforms(DatagramSocket socket, IpSecTransform transform)
             throws IOException {
-        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) {
-            removeTransportModeTransform(pfd, transform);
-        }
+        removeTransportModeTransforms(socket.getFileDescriptor$(), transform);
     }
 
     /**
@@ -454,17 +457,10 @@
      * @param transform the IPsec Transform that was previously applied to the given socket
      * @throws IOException indicating that the transform could not be removed from the socket
      */
-    public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
+    public void removeTransportModeTransforms(FileDescriptor socket, IpSecTransform transform)
             throws IOException {
         try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
-            removeTransportModeTransform(pfd, transform);
-        }
-    }
-
-    /* Call down to remove a transform */
-    private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
-        try {
-            mService.removeTransportModeTransform(pfd, transform.getResourceId());
+            mService.removeTransportModeTransforms(pfd, transform.getResourceId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index 102ba6d..7b9b483 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -38,13 +38,11 @@
 import java.net.InetAddress;
 
 /**
- * This class represents an IPsec transform, which comprises security associations in one or both
- * directions.
+ * This class represents a transform, which roughly corresponds to an IPsec Security Association.
  *
  * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform}
- * object encapsulates the properties and state of an inbound and outbound IPsec security
- * association. That includes, but is not limited to, algorithm choice, key material, and allocated
- * system resources.
+ * object encapsulates the properties and state of an IPsec security association. That includes,
+ * but is not limited to, algorithm choice, key material, and allocated system resources.
  *
  * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
  *     Internet Protocol</a>
@@ -52,23 +50,6 @@
 public final class IpSecTransform implements AutoCloseable {
     private static final String TAG = "IpSecTransform";
 
-    /**
-     * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
-     * applies to traffic towards the host.
-     */
-    public static final int DIRECTION_IN = 0;
-
-    /**
-     * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
-     * applies to traffic from the host.
-     */
-    public static final int DIRECTION_OUT = 1;
-
-    /** @hide */
-    @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface TransformDirection {}
-
     /** @hide */
     public static final int MODE_TRANSPORT = 0;
 
@@ -170,7 +151,7 @@
      *
      * <p>Deactivating a transform while it is still applied to a socket will result in errors on
      * that socket. Make sure to remove transforms by calling {@link
-     * IpSecManager#removeTransportModeTransform}. Note, removing an {@code IpSecTransform} from a
+     * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a
      * socket will not deactivate it (because one transform may be applied to multiple sockets).
      *
      * <p>It is safe to call this method on a transform that has already been deactivated.
@@ -272,85 +253,49 @@
         private IpSecConfig mConfig;
 
         /**
-         * Set the encryption algorithm for the given direction.
-         *
-         * <p>If encryption is set for a direction without also providing an SPI for that direction,
-         * creation of an {@code IpSecTransform} will fail when attempting to build the transform.
+         * Set the encryption algorithm.
          *
          * <p>Encryption is mutually exclusive with authenticated encryption.
          *
-         * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
          */
-        public IpSecTransform.Builder setEncryption(
-                @TransformDirection int direction, IpSecAlgorithm algo) {
+        public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) {
             // TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
-            mConfig.setEncryption(direction, algo);
+            Preconditions.checkNotNull(algo);
+            mConfig.setEncryption(algo);
             return this;
         }
 
         /**
-         * Set the authentication (integrity) algorithm for the given direction.
-         *
-         * <p>If authentication is set for a direction without also providing an SPI for that
-         * direction, creation of an {@code IpSecTransform} will fail when attempting to build the
-         * transform.
+         * Set the authentication (integrity) algorithm.
          *
          * <p>Authentication is mutually exclusive with authenticated encryption.
          *
-         * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
          */
-        public IpSecTransform.Builder setAuthentication(
-                @TransformDirection int direction, IpSecAlgorithm algo) {
+        public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) {
             // TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
-            mConfig.setAuthentication(direction, algo);
+            Preconditions.checkNotNull(algo);
+            mConfig.setAuthentication(algo);
             return this;
         }
 
         /**
-         * Set the authenticated encryption algorithm for the given direction.
+         * Set the authenticated encryption algorithm.
          *
-         * <p>If an authenticated encryption algorithm is set for a given direction without also
-         * providing an SPI for that direction, creation of an {@code IpSecTransform} will fail when
-         * attempting to build the transform.
-         *
-         * <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated
-         * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as
-         * referred to in <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
+         * <p>The Authenticated Encryption (AE) class of algorithms are also known as
+         * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode
+         * algorithms (as referred to in
+         * <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
          *
          * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
          *
-         * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
          *     be applied.
          */
-        public IpSecTransform.Builder setAuthenticatedEncryption(
-                @TransformDirection int direction, IpSecAlgorithm algo) {
-            mConfig.setAuthenticatedEncryption(direction, algo);
-            return this;
-        }
-
-        /**
-         * Set the SPI for the given direction.
-         *
-         * <p>Because IPsec operates at the IP layer, this 32-bit identifier uniquely identifies
-         * packets to a given destination address. To prevent SPI collisions, values should be
-         * reserved by calling {@link IpSecManager#allocateSecurityParameterIndex}.
-         *
-         * <p>If the SPI and algorithms are omitted for one direction, traffic in that direction
-         * will not be encrypted or authenticated.
-         *
-         * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
-         * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
-         *     traffic
-         */
-        public IpSecTransform.Builder setSpi(
-                @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) {
-            if (spi.getResourceId() == INVALID_RESOURCE_ID) {
-                throw new IllegalArgumentException("Invalid SecurityParameterIndex");
-            }
-            mConfig.setSpiResourceId(direction, spi.getResourceId());
+        public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) {
+            Preconditions.checkNotNull(algo);
+            mConfig.setAuthenticatedEncryption(algo);
             return this;
         }
 
@@ -363,7 +308,8 @@
          * @hide
          */
         @SystemApi
-        public IpSecTransform.Builder setUnderlyingNetwork(Network net) {
+        public IpSecTransform.Builder setUnderlyingNetwork(@NonNull Network net) {
+            Preconditions.checkNotNull(net);
             mConfig.setNetwork(net);
             return this;
         }
@@ -382,7 +328,8 @@
          *     encapsulated traffic. In the case of IKEv2, this should be port 4500.
          */
         public IpSecTransform.Builder setIpv4Encapsulation(
-                IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
+                @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
+            Preconditions.checkNotNull(localSocket);
             mConfig.setEncapType(ENCAP_ESPINUDP);
             if (localSocket.getResourceId() == INVALID_RESOURCE_ID) {
                 throw new IllegalArgumentException("Invalid UdpEncapsulationSocket");
@@ -419,24 +366,33 @@
          * will not affect any network traffic until it has been applied to one or more sockets.
          *
          * @see IpSecManager#applyTransportModeTransform
-         * @param remoteAddress the remote {@code InetAddress} of traffic on sockets that will use
-         *     this transform
+         * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use
+         *     this transform; this address must belong to the Network used by all sockets that
+         *     utilize this transform; if provided, then only traffic originating from the
+         *     specified source address will be processed.
+         * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
+         *     traffic
          * @throws IllegalArgumentException indicating that a particular combination of transform
          *     properties is invalid
-         * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms are
-         *     active
+         * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
+         *     are active
          * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
          *     collides with an existing transform
          * @throws IOException indicating other errors
          */
-        public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
+        public IpSecTransform buildTransportModeTransform(
+                @NonNull InetAddress sourceAddress,
+                @NonNull IpSecManager.SecurityParameterIndex spi)
                 throws IpSecManager.ResourceUnavailableException,
                         IpSecManager.SpiUnavailableException, IOException {
-            if (remoteAddress == null) {
-                throw new IllegalArgumentException("Remote address may not be null or empty!");
+            Preconditions.checkNotNull(sourceAddress);
+            Preconditions.checkNotNull(spi);
+            if (spi.getResourceId() == INVALID_RESOURCE_ID) {
+                throw new IllegalArgumentException("Invalid SecurityParameterIndex");
             }
             mConfig.setMode(MODE_TRANSPORT);
-            mConfig.setRemoteAddress(remoteAddress.getHostAddress());
+            mConfig.setSourceAddress(sourceAddress.getHostAddress());
+            mConfig.setSpiResourceId(spi.getResourceId());
             // FIXME: modifying a builder after calling build can change the built transform.
             return new IpSecTransform(mContext, mConfig).activate();
         }
@@ -445,26 +401,33 @@
          * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some
          * parameters have interdependencies that are checked at build time.
          *
-         * @param localAddress the {@link InetAddress} that provides the local endpoint for this
+         * @param sourceAddress the {@link InetAddress} that provides the source address for this
          *     IPsec tunnel. This is almost certainly an address belonging to the {@link Network}
          *     that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}.
-         * @param remoteAddress the {@link InetAddress} representing the remote endpoint of this
-         *     IPsec tunnel.
+         * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
+         *     traffic
          * @throws IllegalArgumentException indicating that a particular combination of transform
          *     properties is invalid.
+         * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
+         *     are active
+         * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
+         *     collides with an existing transform
+         * @throws IOException indicating other errors
          * @hide
          */
         public IpSecTransform buildTunnelModeTransform(
-                InetAddress localAddress, InetAddress remoteAddress) {
-            if (localAddress == null) {
-                throw new IllegalArgumentException("Local address may not be null or empty!");
+                @NonNull InetAddress sourceAddress,
+                @NonNull IpSecManager.SecurityParameterIndex spi)
+                throws IpSecManager.ResourceUnavailableException,
+                        IpSecManager.SpiUnavailableException, IOException {
+            Preconditions.checkNotNull(sourceAddress);
+            Preconditions.checkNotNull(spi);
+            if (spi.getResourceId() == INVALID_RESOURCE_ID) {
+                throw new IllegalArgumentException("Invalid SecurityParameterIndex");
             }
-            if (remoteAddress == null) {
-                throw new IllegalArgumentException("Remote address may not be null or empty!");
-            }
-            mConfig.setLocalAddress(localAddress.getHostAddress());
-            mConfig.setRemoteAddress(remoteAddress.getHostAddress());
             mConfig.setMode(MODE_TUNNEL);
+            mConfig.setSourceAddress(sourceAddress.getHostAddress());
+            mConfig.setSpiResourceId(spi.getResourceId());
             return new IpSecTransform(mContext, mConfig);
         }
 
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/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 81c49a3..9ef26a9 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -29,7 +29,6 @@
 import android.net.wifi.WifiInfo;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.telephony.SubscriptionPlan;
 import android.util.DebugUtils;
 import android.util.Pair;
 
@@ -329,7 +328,7 @@
      * to access network when the device is idle or in battery saver mode. Otherwise, false.
      */
     public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) {
-        return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+        return procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
     }
 
     /**
@@ -337,7 +336,7 @@
      * to access network when the device is in data saver mode. Otherwise, false.
      */
     public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) {
-        return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+        return procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
     }
 
     public static String resolveNetworkId(WifiConfiguration config) {
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/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 4e94c32..7b0c153 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -224,6 +224,34 @@
     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 changing screen off timeout.
+     *
+     * <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_SCREEN_TIMEOUT = "no_config_screen_timeout";
+
+    /**
      * 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>.
@@ -892,6 +920,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.
      *
@@ -2209,6 +2258,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.
@@ -2234,22 +2289,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 db32144..6d91f59 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6830,7 +6830,7 @@
          * @hide
          */
         public static final int SHOW_ROTATION_SUGGESTIONS_DEFAULT =
-                SHOW_ROTATION_SUGGESTIONS_DISABLED;
+                SHOW_ROTATION_SUGGESTIONS_ENABLED;
 
         /**
          * Read only list of the service components that the current user has explicitly allowed to
@@ -9653,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>
@@ -10058,6 +10069,7 @@
          * If 1 low power mode is enabled.
          * @hide
          */
+        @TestApi
         public static final String LOW_POWER_MODE = "low_power";
 
         /**
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/keystore/KeyDerivationParameters.aidl b/core/java/android/security/keystore/KeyDerivationParams.aidl
similarity index 94%
rename from core/java/android/security/keystore/KeyDerivationParameters.aidl
rename to core/java/android/security/keystore/KeyDerivationParams.aidl
index 92316e5..f39aa04 100644
--- a/core/java/android/security/keystore/KeyDerivationParameters.aidl
+++ b/core/java/android/security/keystore/KeyDerivationParams.aidl
@@ -17,4 +17,4 @@
 package android.security.keystore;
 
 /* @hide */
-parcelable KeyDerivationParameters;
+parcelable KeyDerivationParams;
diff --git a/core/java/android/security/keystore/KeyDerivationParameters.java b/core/java/android/security/keystore/KeyDerivationParams.java
similarity index 71%
rename from core/java/android/security/keystore/KeyDerivationParameters.java
rename to core/java/android/security/keystore/KeyDerivationParams.java
index 762f62a..b702acc 100644
--- a/core/java/android/security/keystore/KeyDerivationParameters.java
+++ b/core/java/android/security/keystore/KeyDerivationParams.java
@@ -28,27 +28,22 @@
 
 /**
  * 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 {
     }
 
     /**
      * Salted SHA256
-     * @hide
      */
     public static final int ALGORITHM_SHA256 = 1;
 
@@ -62,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);
     }
@@ -85,14 +80,14 @@
         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];
         }
     };
 
@@ -108,7 +103,7 @@
     /**
      * @hide
      */
-    protected KeyDerivationParameters(Parcel in) {
+    protected KeyDerivationParams(Parcel in) {
         mAlgorithm = in.readInt();
         mSalt = in.createByteArray();
     }
diff --git a/core/java/android/security/keystore/RecoveryData.java b/core/java/android/security/keystore/RecoveryData.java
index c717d9a..897aa18 100644
--- a/core/java/android/security/keystore/RecoveryData.java
+++ b/core/java/android/security/keystore/RecoveryData.java
@@ -38,7 +38,7 @@
  * @hide
  */
 public final class RecoveryData implements Parcelable {
-    private Integer mSnapshotVersion;
+    private int mSnapshotVersion;
     private List<RecoveryMetadata> mRecoveryMetadata;
     private List<EntryRecoveryData> mEntryRecoveryData;
     private byte[] mEncryptedRecoveryKeyBlob;
@@ -163,7 +163,6 @@
          * @throws NullPointerException if some required fields were not set.
          */
         public @NonNull RecoveryData build() {
-            Preconditions.checkNotNull(mInstance.mSnapshotVersion);
             Preconditions.checkCollectionElementsNotNull(mInstance.mRecoveryMetadata,
                     "recoveryMetadata");
             Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData,
diff --git a/core/java/android/security/keystore/RecoveryManager.java b/core/java/android/security/keystore/RecoveryManager.java
index d00f43f..99bd284 100644
--- a/core/java/android/security/keystore/RecoveryManager.java
+++ b/core/java/android/security/keystore/RecoveryManager.java
@@ -23,8 +23,6 @@
 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;
 
@@ -39,64 +37,6 @@
  */
 public class RecoveryManager {
 
-    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. */
@@ -122,47 +62,6 @@
     }
 
     /**
-     * Exceptions returned by {@link RecoveryManager}.
-     */
-    public static class RecoveryManagerException extends AndroidException {
-        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;
-        }
-    }
-
-    /**
      * 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
@@ -260,15 +159,14 @@
      * {@code RecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
      * in vaultParams {@link #startRecoverySession}
      *
-     * @param serverParameters included in recovery key blob.
+     * @param serverParams included in recovery key blob.
      * @see #getRecoveryData
      * @throws RecoveryManagerException If parameters rotation is rate limited.
      * @hide
      */
-    public void setServerParameters(long serverParameters)
-            throws RecoveryManagerException {
+    public void setServerParams(byte[] serverParams) throws RecoveryManagerException {
         try {
-            mBinder.setServerParameters(serverParameters);
+            mBinder.setServerParams(serverParams);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
@@ -309,20 +207,17 @@
      *   <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)
+    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);
+                    (Map<String, Integer>) mBinder.getRecoveryStatus(/*packageName=*/ null);
             return result;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -414,8 +309,9 @@
      * 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 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
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/keystore/RecoveryMetadata.java b/core/java/android/security/keystore/RecoveryMetadata.java
index bfd558e..3f09455 100644
--- a/core/java/android/security/keystore/RecoveryMetadata.java
+++ b/core/java/android/security/keystore/RecoveryMetadata.java
@@ -78,9 +78,9 @@
     private Integer mLockScreenUiFormat;
 
     /**
-     * Parameters of key derivation function, including algorithm, difficulty, salt.
+     * Parameters of the key derivation function, including algorithm, difficulty, salt.
      */
-    private KeyDerivationParameters mKeyDerivationParameters;
+    private KeyDerivationParams mKeyDerivationParams;
     private byte[] mSecret; // Derived from user secret. The field must have limited visibility.
 
     /**
@@ -90,11 +90,11 @@
      */
     public RecoveryMetadata(@UserSecretType int userSecretType,
             @LockScreenUiFormat int lockScreenUiFormat,
-            @NonNull KeyDerivationParameters keyDerivationParameters,
+            @NonNull KeyDerivationParams keyDerivationParams,
             @NonNull byte[] secret) {
         mUserSecretType = userSecretType;
         mLockScreenUiFormat = lockScreenUiFormat;
-        mKeyDerivationParameters = Preconditions.checkNotNull(keyDerivationParameters);
+        mKeyDerivationParams = Preconditions.checkNotNull(keyDerivationParams);
         mSecret = Preconditions.checkNotNull(secret);
     }
 
@@ -126,8 +126,8 @@
      * Specifies function used to derive symmetric key from user input
      * Format is defined in separate util class.
      */
-    public @NonNull KeyDerivationParameters getKeyDerivationParameters() {
-        return mKeyDerivationParameters;
+    public @NonNull KeyDerivationParams getKeyDerivationParams() {
+        return mKeyDerivationParams;
     }
 
     /**
@@ -176,12 +176,12 @@
         /**
          * Sets parameters of the key derivation function.
          *
-         * @param keyDerivationParameters Key derivation parameters
+         * @param keyDerivationParams Key derivation Params
          * @return This builder.
          */
-        public Builder setKeyDerivationParameters(@NonNull KeyDerivationParameters
-                keyDerivationParameters) {
-            mInstance.mKeyDerivationParameters = keyDerivationParameters;
+        public Builder setKeyDerivationParams(@NonNull KeyDerivationParams
+                keyDerivationParams) {
+            mInstance.mKeyDerivationParams = keyDerivationParams;
             return this;
         }
 
@@ -210,7 +210,7 @@
                 mInstance.mUserSecretType = TYPE_LOCKSCREEN;
             }
             Preconditions.checkNotNull(mInstance.mLockScreenUiFormat);
-            Preconditions.checkNotNull(mInstance.mKeyDerivationParameters);
+            Preconditions.checkNotNull(mInstance.mKeyDerivationParams);
             if (mInstance.mSecret == null) {
                 mInstance.mSecret = new byte[]{};
             }
@@ -253,7 +253,7 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mUserSecretType);
         out.writeInt(mLockScreenUiFormat);
-        out.writeTypedObject(mKeyDerivationParameters, flags);
+        out.writeTypedObject(mKeyDerivationParams, flags);
         out.writeByteArray(mSecret);
     }
 
@@ -263,7 +263,7 @@
     protected RecoveryMetadata(Parcel in) {
         mUserSecretType = in.readInt();
         mLockScreenUiFormat = in.readInt();
-        mKeyDerivationParameters = in.readTypedObject(KeyDerivationParameters.CREATOR);
+        mKeyDerivationParams = in.readTypedObject(KeyDerivationParams.CREATOR);
         mSecret = in.createByteArray();
     }
 
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 20cd906..b7b2b2d 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -55,6 +55,7 @@
 import android.widget.RemoteViews;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 
 import java.lang.annotation.Retention;
@@ -1543,7 +1544,11 @@
             return mShowBadge;
         }
 
-        private void populate(String key, int rank, boolean matchesInterruptionFilter,
+        /**
+         * @hide
+         */
+        @VisibleForTesting
+        public void populate(String key, int rank, boolean matchesInterruptionFilter,
                 int visibilityOverride, int suppressedVisualEffects, int importance,
                 CharSequence explanation, String overrideGroupKey,
                 NotificationChannel channel, ArrayList<String> overridePeople,
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 914017f..8242156 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -38,8 +38,7 @@
     static {
         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");
diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java
index c25b272..e0d085c 100644
--- a/core/java/android/util/StatsManager.java
+++ b/core/java/android/util/StatsManager.java
@@ -42,6 +42,16 @@
     }
 
     /**
+     * Temporary to prevent build failures. Will be deleted.
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) {
+        // To prevent breakages of dependencies on old API.
+
+        return false;
+    }
+
+    /**
      * Clients can send a configuration and simultaneously registers the name of a broadcast
      * receiver that listens for when it should request data.
      *
@@ -70,6 +80,15 @@
     }
 
     /**
+     * Temporary to prevent build failures. Will be deleted.
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public boolean removeConfiguration(String configKey) {
+        // To prevent breakages of old dependencies.
+        return false;
+    }
+
+    /**
      * Remove a configuration from logging.
      *
      * @param configKey Configuration key to remove.
@@ -93,6 +112,16 @@
     }
 
     /**
+     * Temporary to prevent build failures. Will be deleted.
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public byte[] getData(String configKey) {
+        // TODO: remove this and all other methods with String-based config keys.
+        // To prevent build breakages of dependencies.
+        return null;
+    }
+
+    /**
      * Clients can request data with a binder call. This getter is destructive and also clears
      * the retrieved metrics from statsd memory.
      *
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/security/keystore/KeyDerivationParameters.aidl b/core/java/android/view/IRemoteAnimationFinishedCallback.aidl
similarity index 61%
copy from core/java/android/security/keystore/KeyDerivationParameters.aidl
copy to core/java/android/view/IRemoteAnimationFinishedCallback.aidl
index 92316e5..ae58b22 100644
--- a/core/java/android/security/keystore/KeyDerivationParameters.aidl
+++ b/core/java/android/view/IRemoteAnimationFinishedCallback.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.
@@ -11,10 +11,17 @@
  * 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.
+ * limitations under the License
  */
 
-package android.security.keystore;
+package android.view;
 
-/* @hide */
-parcelable KeyDerivationParameters;
+/**
+ * Interface to be invoked by the controlling process when a remote animation has finished.
+ *
+ * @see IRemoteAnimationRunner
+ * {@hide}
+ */
+interface IRemoteAnimationFinishedCallback {
+    void onAnimationFinished();
+}
diff --git a/core/java/android/view/IRemoteAnimationRunner.aidl b/core/java/android/view/IRemoteAnimationRunner.aidl
new file mode 100644
index 0000000..1350ebf
--- /dev/null
+++ b/core/java/android/view/IRemoteAnimationRunner.aidl
@@ -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.
+ */
+
+package android.view;
+
+import android.view.RemoteAnimationTarget;
+import android.view.IRemoteAnimationFinishedCallback;
+
+/**
+ * Interface that is used to callback from window manager to the process that runs a remote
+ * animation to start or cancel it.
+ *
+ * {@hide}
+ */
+oneway interface IRemoteAnimationRunner {
+
+    /**
+     * Called when the process needs to start the remote animation.
+     *
+     * @param apps The list of apps to animate.
+     * @param finishedCallback The callback to invoke when the animation is finished.
+     */
+    void onAnimationStart(in RemoteAnimationTarget[] apps,
+            in IRemoteAnimationFinishedCallback finishedCallback);
+
+    /**
+     * Called when the animation was cancelled. From this point on, any updates onto the leashes
+     * won't have any effect anymore.
+     */
+    void onAnimationCancelled();
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7001067..4adcb8f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -38,6 +38,7 @@
 import android.view.IDockedStackListener;
 import android.view.IOnKeyguardExitResult;
 import android.view.IPinnedStackListener;
+import android.view.RemoteAnimationAdapter;
 import android.view.IRotationWatcher;
 import android.view.IWallpaperVisibilityListener;
 import android.view.IWindowSession;
@@ -124,6 +125,7 @@
     void overridePendingAppTransitionMultiThumbFuture(
             IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback startedCallback,
             boolean scaleUp);
+    void overridePendingAppTransitionRemote(in RemoteAnimationAdapter remoteAnimationAdapter);
     void executeAppTransition();
 
     /** Used by system ui to report that recents has shown itself. */
@@ -147,7 +149,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);
@@ -294,6 +296,11 @@
     boolean hasNavigationBar();
 
     /**
+    * Get the position of the nav bar
+    */
+    int getNavBarPosition();
+
+    /**
      * Lock the device immediately with the specified options (can be null).
      */
     void lockNow(in Bundle options);
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/telephony/java/android/telephony/data/InterfaceAddress.aidl b/core/java/android/view/RemoteAnimationAdapter.aidl
similarity index 76%
copy from telephony/java/android/telephony/data/InterfaceAddress.aidl
copy to core/java/android/view/RemoteAnimationAdapter.aidl
index d750363..855bc74 100644
--- a/telephony/java/android/telephony/data/InterfaceAddress.aidl
+++ b/core/java/android/view/RemoteAnimationAdapter.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.
@@ -11,10 +11,9 @@
  * 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.
+ * limitations under the License
  */
 
-/** @hide */
-package android.telephony.data;
+package android.view;
 
-parcelable InterfaceAddress;
+parcelable RemoteAnimationAdapter;
diff --git a/core/java/android/view/RemoteAnimationAdapter.java b/core/java/android/view/RemoteAnimationAdapter.java
new file mode 100644
index 0000000..d597e59
--- /dev/null
+++ b/core/java/android/view/RemoteAnimationAdapter.java
@@ -0,0 +1,108 @@
+/*
+ * 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.view;
+
+import android.app.ActivityOptions;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Object that describes how to run a remote animation.
+ * <p>
+ * A remote animation lets another app control the entire app transition. It does so by
+ * <ul>
+ *     <li>using {@link ActivityOptions#makeRemoteAnimation}</li>
+ *     <li>using {@link IWindowManager#overridePendingAppTransitionRemote}</li>
+ * </ul>
+ * to register a {@link RemoteAnimationAdapter} that describes how the animation should be run:
+ * Along some meta-data, this object contains a callback that gets invoked from window manager when
+ * the transition is ready to be started.
+ * <p>
+ * Window manager supplies a list of {@link RemoteAnimationTarget}s into the callback. Each target
+ * contains information about the activity that is animating as well as
+ * {@link RemoteAnimationTarget#leash}. The controlling app can modify the leash like any other
+ * {@link SurfaceControl}, including the possibility to synchronize updating the leash's surface
+ * properties with a frame to be drawn using
+ * {@link SurfaceControl.Transaction#deferTransactionUntil}.
+ * <p>
+ * When the animation is done, the controlling app can invoke
+ * {@link IRemoteAnimationFinishedCallback} that gets supplied into
+ * {@link IRemoteAnimationRunner#onStartAnimation}
+ *
+ * @hide
+ */
+public class RemoteAnimationAdapter implements Parcelable {
+
+    private final IRemoteAnimationRunner mRunner;
+    private final long mDuration;
+    private final long mStatusBarTransitionDelay;
+
+    /**
+     * @param runner The interface that gets notified when we actually need to start the animation.
+     * @param duration The duration of the animation.
+     * @param statusBarTransitionDelay The desired delay for all visual animations in the
+     *        status bar caused by this app animation in millis.
+     */
+    public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration,
+            long statusBarTransitionDelay) {
+        mRunner = runner;
+        mDuration = duration;
+        mStatusBarTransitionDelay = statusBarTransitionDelay;
+    }
+
+    public RemoteAnimationAdapter(Parcel in) {
+        mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder());
+        mDuration = in.readLong();
+        mStatusBarTransitionDelay = in.readLong();
+    }
+
+    public IRemoteAnimationRunner getRunner() {
+        return mRunner;
+    }
+
+    public long getDuration() {
+        return mDuration;
+    }
+
+    public long getStatusBarTransitionDelay() {
+        return mStatusBarTransitionDelay;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongInterface(mRunner);
+        dest.writeLong(mDuration);
+        dest.writeLong(mStatusBarTransitionDelay);
+    }
+
+    public static final Creator<RemoteAnimationAdapter> CREATOR
+            = new Creator<RemoteAnimationAdapter>() {
+        public RemoteAnimationAdapter createFromParcel(Parcel in) {
+            return new RemoteAnimationAdapter(in);
+        }
+
+        public RemoteAnimationAdapter[] newArray(int size) {
+            return new RemoteAnimationAdapter[size];
+        }
+    };
+}
diff --git a/telephony/java/android/telephony/data/InterfaceAddress.aidl b/core/java/android/view/RemoteAnimationTarget.aidl
similarity index 76%
copy from telephony/java/android/telephony/data/InterfaceAddress.aidl
copy to core/java/android/view/RemoteAnimationTarget.aidl
index d750363..769bf5e 100644
--- a/telephony/java/android/telephony/data/InterfaceAddress.aidl
+++ b/core/java/android/view/RemoteAnimationTarget.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.
@@ -11,10 +11,9 @@
  * 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.
+ * limitations under the License
  */
 
-/** @hide */
-package android.telephony.data;
+package android.view;
 
-parcelable InterfaceAddress;
+parcelable RemoteAnimationTarget;
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
new file mode 100644
index 0000000..f39e618
--- /dev/null
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -0,0 +1,151 @@
+/*
+ * 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.view;
+
+import android.annotation.IntDef;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Describes an activity to be animated as part of a remote animation.
+ *
+ * @hide
+ */
+public class RemoteAnimationTarget implements Parcelable {
+
+    /**
+     * The app is in the set of opening apps of this transition.
+     */
+    public static final int MODE_OPENING = 0;
+
+    /**
+     * The app is in the set of closing apps of this transition.
+     */
+    public static final int MODE_CLOSING = 1;
+
+    @IntDef(prefix = { "MODE_" }, value = {
+            MODE_OPENING,
+            MODE_CLOSING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Mode {}
+
+    /**
+     * The {@link Mode} to describe whether this app is opening or closing.
+     */
+    public final @Mode int mode;
+
+    /**
+     * The id of the task this app belongs to.
+     */
+    public final int taskId;
+
+    /**
+     * The {@link SurfaceControl} object to actually control the transform of the app.
+     */
+    public final SurfaceControl leash;
+
+    /**
+     * Whether the app is translucent and may reveal apps behind.
+     */
+    public final boolean isTranslucent;
+
+    /**
+     * The clip rect window manager applies when clipping the app's main surface in screen space
+     * coordinates. This is just a hint to the animation runner: If running a clip-rect animation,
+     * anything that extends beyond these bounds will not have any effect. This implies that any
+     * clip-rect animation should likely stop at these bounds.
+     */
+    public final Rect clipRect;
+
+    /**
+     * The index of the element in the tree in prefix order. This should be used for z-layering
+     * to preserve original z-layer order in the hierarchy tree assuming no "boosting" needs to
+     * happen.
+     */
+    public final int prefixOrderIndex;
+
+    /**
+     * The source position of the app, in screen spaces coordinates. If the position of the leash
+     * is modified from the controlling app, any animation transform needs to be offset by this
+     * amount.
+     */
+    public final Point position;
+
+    /**
+     * The bounds of the source container the app lives in, in screen space coordinates. If the crop
+     * of the leash is modified from the controlling app, it needs to take the source container
+     * bounds into account when calculating the crop.
+     */
+    public final Rect sourceContainerBounds;
+
+    public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
+            Rect clipRect, int prefixOrderIndex, Point position, Rect sourceContainerBounds) {
+        this.mode = mode;
+        this.taskId = taskId;
+        this.leash = leash;
+        this.isTranslucent = isTranslucent;
+        this.clipRect = new Rect(clipRect);
+        this.prefixOrderIndex = prefixOrderIndex;
+        this.position = new Point(position);
+        this.sourceContainerBounds = new Rect(sourceContainerBounds);
+    }
+
+    public RemoteAnimationTarget(Parcel in) {
+        taskId = in.readInt();
+        mode = in.readInt();
+        leash = in.readParcelable(null);
+        isTranslucent = in.readBoolean();
+        clipRect = in.readParcelable(null);
+        prefixOrderIndex = in.readInt();
+        position = in.readParcelable(null);
+        sourceContainerBounds = in.readParcelable(null);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(taskId);
+        dest.writeInt(mode);
+        dest.writeParcelable(leash, 0 /* flags */);
+        dest.writeBoolean(isTranslucent);
+        dest.writeParcelable(clipRect, 0 /* flags */);
+        dest.writeInt(prefixOrderIndex);
+        dest.writeParcelable(position, 0 /* flags */);
+        dest.writeParcelable(sourceContainerBounds, 0 /* flags */);
+    }
+
+    public static final Creator<RemoteAnimationTarget> CREATOR
+            = new Creator<RemoteAnimationTarget>() {
+        public RemoteAnimationTarget createFromParcel(Parcel in) {
+            return new RemoteAnimationTarget(in);
+        }
+
+        public RemoteAnimationTarget[] newArray(int size) {
+            return new RemoteAnimationTarget[size];
+        }
+    };
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ad71b58..bd3be1b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3226,6 +3226,11 @@
      */
     private static final int PFLAG3_SCREEN_READER_FOCUSABLE = 0x10000000;
 
+    /**
+     * The last aggregated visibility. Used to detect when it truly changes.
+     */
+    private static final int PFLAG3_AGGREGATED_VISIBLE = 0x20000000;
+
     /* End of masks for mPrivateFlags3 */
 
     /**
@@ -3387,6 +3392,18 @@
      * decorations when they are shown.  You can perform layout of your inner
      * UI elements to account for non-fullscreen system UI through the
      * {@link #fitSystemWindows(Rect)} method.
+     *
+     * <p>Note: on displays that have a {@link DisplayCutout}, the window may still be placed
+     *  differently than if {@link #SYSTEM_UI_FLAG_FULLSCREEN} was set, if the
+     *  window's {@link WindowManager.LayoutParams#layoutInDisplayCutoutMode
+     *  layoutInDisplayCutoutMode} is
+     *  {@link WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
+     *  LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}. To avoid this, use either of the other modes.
+     *
+     * @see WindowManager.LayoutParams#layoutInDisplayCutoutMode
+     * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
+     * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+     * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
      */
     public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400;
 
@@ -7354,7 +7371,12 @@
      * @hide
      */
     public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
-        if (!isShown()) {
+        // Panes disappearing are relevant even if though the view is no longer visible.
+        boolean isWindowStateChanged =
+                (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        boolean isWindowDisappearedEvent = isWindowStateChanged && ((event.getContentChangeTypes()
+                & AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED) != 0);
+        if (!isShown() && !isWindowDisappearedEvent) {
             return;
         }
         onInitializeAccessibilityEvent(event);
@@ -7462,6 +7484,10 @@
      * @hide
      */
     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+        if ((event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
+                && !TextUtils.isEmpty(getAccessibilityPaneTitle())) {
+            event.getText().add(getAccessibilityPaneTitle());
+        }
     }
 
     /**
@@ -11587,6 +11613,23 @@
         if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
             return;
         }
+        // Changes to views with a pane title count as window state changes, as the pane title
+        // marks them as significant parts of the UI.
+        if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) {
+            final AccessibilityEvent event = AccessibilityEvent.obtain();
+            event.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+            event.setContentChangeTypes(changeType);
+            onPopulateAccessibilityEvent(event);
+            if (mParent != null) {
+                try {
+                    mParent.requestSendAccessibilityEvent(this, event);
+                } 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);
@@ -12520,6 +12563,10 @@
      */
     @CallSuper
     public void onVisibilityAggregated(boolean isVisible) {
+        // Update our internal visibility tracking so we can detect changes
+        boolean oldVisible = (mPrivateFlags3 & PFLAG3_AGGREGATED_VISIBLE) != 0;
+        mPrivateFlags3 = isVisible ? (mPrivateFlags3 | PFLAG3_AGGREGATED_VISIBLE)
+                : (mPrivateFlags3 & ~PFLAG3_AGGREGATED_VISIBLE);
         if (isVisible && mAttachInfo != null) {
             initialAwakenScrollBars();
         }
@@ -12560,6 +12607,13 @@
                 }
             }
         }
+        if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) {
+            if (isVisible != oldVisible) {
+                notifyAccessibilityStateChanged(isVisible
+                        ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
+                        : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f81a4c3..fe3b696 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -20,7 +20,7 @@
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
 import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
-import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
@@ -1596,9 +1596,9 @@
 
     void dispatchApplyInsets(View host) {
         WindowInsets insets = getWindowInsets(true /* forceConstruct */);
-        final boolean layoutInCutout =
-                (mWindowAttributes.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0;
-        if (!layoutInCutout) {
+        final boolean dispatchCutout = (mWindowAttributes.layoutInDisplayCutoutMode
+                == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS);
+        if (!dispatchCutout) {
             // Window is either not laid out in cutout or the status bar inset takes care of
             // clearing the cutout, so we don't need to dispatch the cutout to the hierarchy.
             insets = insets.consumeDisplayCutout();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index a65aba1..50d7118 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -21,7 +21,6 @@
 import static android.view.WindowLayoutParamsProto.BUTTON_BRIGHTNESS;
 import static android.view.WindowLayoutParamsProto.COLOR_MODE;
 import static android.view.WindowLayoutParamsProto.FLAGS;
-import static android.view.WindowLayoutParamsProto.FLAGS_EXTRA;
 import static android.view.WindowLayoutParamsProto.FORMAT;
 import static android.view.WindowLayoutParamsProto.GRAVITY;
 import static android.view.WindowLayoutParamsProto.HAS_SYSTEM_UI_LISTENERS;
@@ -46,7 +45,6 @@
 
 import android.Manifest.permission;
 import android.annotation.IntDef;
-import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -889,7 +887,12 @@
          *  decorations around the border (such as the status bar).  The
          *  window must correctly position its contents to take the screen
          *  decoration into account.  This flag is normally set for you
-         *  by Window as described in {@link Window#setFlags}. */
+         *  by Window as described in {@link Window#setFlags}.
+         *
+         *  <p>Note: on displays that have a {@link DisplayCutout}, the window may be placed
+         *  such that it avoids the {@link DisplayCutout} area if necessary according to the
+         *  {@link #layoutInDisplayCutoutMode}.
+         */
         public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
 
         /** Window flag: allow window to extend outside of the screen. */
@@ -1295,33 +1298,6 @@
         }, formatToHexString = true)
         public int flags;
 
-        /** @hide */
-        @Retention(RetentionPolicy.SOURCE)
-        @LongDef(
-            flag = true,
-            value = {
-                    LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA,
-            })
-        @interface Flags2 {}
-
-        /**
-         * Window flag: allow placing the window within the area that overlaps with the
-         * display cutout.
-         *
-         * <p>
-         * The window must correctly position its contents to take the display cutout into account.
-         *
-         * @see DisplayCutout
-         */
-        public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 0x00000001;
-
-        /**
-         * Various behavioral options/flags.  Default is none.
-         *
-         * @see #FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA
-         */
-        @Flags2 public long flags2;
-
         /**
          * If the window has requested hardware acceleration, but this is not
          * allowed in the process it is in, then still render it as if it is
@@ -2050,6 +2026,77 @@
          */
         public boolean hasSystemUiListeners;
 
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(
+                flag = true,
+                value = {LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
+                        LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS,
+                        LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER})
+        @interface LayoutInDisplayCutoutMode {}
+
+        /**
+         * Controls how the window is laid out if there is a {@link DisplayCutout}.
+         *
+         * <p>
+         * Defaults to {@link #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}.
+         *
+         * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
+         * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+         * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
+         * @see DisplayCutout
+         */
+        @LayoutInDisplayCutoutMode
+        public int layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+
+        /**
+         * The window is allowed to extend into the {@link DisplayCutout} area, only if the
+         * {@link DisplayCutout} is fully contained within the status bar. Otherwise, the window is
+         * laid out such that it does not overlap with the {@link DisplayCutout} area.
+         *
+         * <p>
+         * In practice, this means that if the window did not set FLAG_FULLSCREEN or
+         * SYSTEM_UI_FLAG_FULLSCREEN, it can extend into the cutout area in portrait.
+         * Otherwise (i.e. fullscreen or landscape) it is laid out such that it does overlap the
+         * cutout area.
+         *
+         * <p>
+         * The usual precautions for not overlapping with the status bar are sufficient for ensuring
+         * that no important content overlaps with the DisplayCutout.
+         *
+         * @see DisplayCutout
+         * @see WindowInsets
+         */
+        public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0;
+
+        /**
+         * The window is always allowed to extend into the {@link DisplayCutout} area,
+         * even if fullscreen or in landscape.
+         *
+         * <p>
+         * The window must make sure that no important content overlaps with the
+         * {@link DisplayCutout}.
+         *
+         * @see DisplayCutout
+         * @see WindowInsets#getDisplayCutout()
+         */
+        public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1;
+
+        /**
+         * The window is never allowed to overlap with the DisplayCutout area.
+         *
+         * <p>
+         * This should be used with windows that transiently set SYSTEM_UI_FLAG_FULLSCREEN to
+         * avoid a relayout of the window when the flag is set or cleared.
+         *
+         * @see DisplayCutout
+         * @see View#SYSTEM_UI_FLAG_FULLSCREEN SYSTEM_UI_FLAG_FULLSCREEN
+         * @see View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+         */
+        public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2;
+
+
         /**
          * When this window has focus, disable touch pad pointer gesture processing.
          * The window will receive raw position updates from the touch pad instead
@@ -2273,9 +2320,9 @@
             out.writeInt(y);
             out.writeInt(type);
             out.writeInt(flags);
-            out.writeLong(flags2);
             out.writeInt(privateFlags);
             out.writeInt(softInputMode);
+            out.writeInt(layoutInDisplayCutoutMode);
             out.writeInt(gravity);
             out.writeFloat(horizontalMargin);
             out.writeFloat(verticalMargin);
@@ -2329,9 +2376,9 @@
             y = in.readInt();
             type = in.readInt();
             flags = in.readInt();
-            flags2 = in.readLong();
             privateFlags = in.readInt();
             softInputMode = in.readInt();
+            layoutInDisplayCutoutMode = in.readInt();
             gravity = in.readInt();
             horizontalMargin = in.readFloat();
             verticalMargin = in.readFloat();
@@ -2462,10 +2509,6 @@
                 flags = o.flags;
                 changes |= FLAGS_CHANGED;
             }
-            if (flags2 != o.flags2) {
-                flags2 = o.flags2;
-                changes |= FLAGS_CHANGED;
-            }
             if (privateFlags != o.privateFlags) {
                 privateFlags = o.privateFlags;
                 changes |= PRIVATE_FLAGS_CHANGED;
@@ -2474,6 +2517,10 @@
                 softInputMode = o.softInputMode;
                 changes |= SOFT_INPUT_MODE_CHANGED;
             }
+            if (layoutInDisplayCutoutMode != o.layoutInDisplayCutoutMode) {
+                layoutInDisplayCutoutMode = o.layoutInDisplayCutoutMode;
+                changes |= LAYOUT_CHANGED;
+            }
             if (gravity != o.gravity) {
                 gravity = o.gravity;
                 changes |= LAYOUT_CHANGED;
@@ -2651,6 +2698,10 @@
                 sb.append(softInputModeToString(softInputMode));
                 sb.append('}');
             }
+            if (layoutInDisplayCutoutMode != 0) {
+                sb.append(" layoutInDisplayCutoutMode=");
+                sb.append(layoutInDisplayCutoutModeToString(layoutInDisplayCutoutMode));
+            }
             sb.append(" ty=");
             sb.append(ViewDebug.intToString(LayoutParams.class, "type", type));
             if (format != PixelFormat.OPAQUE) {
@@ -2719,11 +2770,6 @@
             sb.append(System.lineSeparator());
             sb.append(prefix).append("  fl=").append(
                     ViewDebug.flagsToString(LayoutParams.class, "flags", flags));
-            if (flags2 != 0) {
-                sb.append(System.lineSeparator());
-                // TODO(roosa): add a long overload for ViewDebug.flagsToString.
-                sb.append(prefix).append("  fl2=0x").append(Long.toHexString(flags2));
-            }
             if (privateFlags != 0) {
                 sb.append(System.lineSeparator());
                 sb.append(prefix).append("  pfl=").append(ViewDebug.flagsToString(
@@ -2771,7 +2817,6 @@
             proto.write(NEEDS_MENU_KEY, needsMenuKey);
             proto.write(COLOR_MODE, mColorMode);
             proto.write(FLAGS, flags);
-            proto.write(FLAGS_EXTRA, flags2);
             proto.write(PRIVATE_FLAGS, privateFlags);
             proto.write(SYSTEM_UI_VISIBILITY_FLAGS, systemUiVisibility);
             proto.write(SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS, subtreeSystemUiVisibility);
@@ -2849,6 +2894,20 @@
                     && height == WindowManager.LayoutParams.MATCH_PARENT;
         }
 
+        private static String layoutInDisplayCutoutModeToString(
+                @LayoutInDisplayCutoutMode int mode) {
+            switch (mode) {
+                case LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:
+                    return "default";
+                case LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS:
+                    return "always";
+                case LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:
+                    return "never";
+                default:
+                    return "unknown(" + mode + ")";
+            }
+        }
+
         private static String softInputModeToString(@SoftInputModeFlags int softInputMode) {
             final StringBuilder result = new StringBuilder();
             final int state = softInputMode & SOFT_INPUT_MASK_STATE;
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 9c1c9e3..a6f36bb 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -45,6 +45,11 @@
     int PRESENCE_INTERNAL = 1 << 0;
     int PRESENCE_EXTERNAL = 1 << 1;
 
+    // Navigation bar position values
+    int NAV_BAR_LEFT = 1 << 0;
+    int NAV_BAR_RIGHT = 1 << 1;
+    int NAV_BAR_BOTTOM = 1 << 2;
+
     /**
      * Sticky broadcast of the current HDMI plugged state.
      */
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index aa61926..e0f74a7 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -192,9 +192,11 @@
  * <b>TRANSITION TYPES</b></br>
  * </p>
  * <p>
- * <b>Window state changed</b> - represents the event of opening a
- * {@link android.widget.PopupWindow}, {@link android.view.Menu},
- * {@link android.app.Dialog}, etc.</br>
+ * <b>Window state changed</b> - represents the event of a change to a section of
+ * the user interface that is visually distinct. Should be sent from either the
+ * root view of a window or from a view that is marked as a pane
+ * {@link android.view.View#setAccessibilityPaneTitle(CharSequence)}. Not that changes
+ * to true windows are represented by {@link #TYPE_WINDOWS_CHANGED}.</br>
  * <em>Type:</em> {@link #TYPE_WINDOW_STATE_CHANGED}</br>
  * <em>Properties:</em></br>
  * <ul>
@@ -203,7 +205,7 @@
  *   <li>{@link #getClassName()} - The class name of the source.</li>
  *   <li>{@link #getPackageName()} - The package name of the source.</li>
  *   <li>{@link #getEventTime()}  - The event time.</li>
- *   <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ *   <li>{@link #getText()} - The text of the source's sub-tree, including the pane titles.</li>
  * </ul>
  * </p>
  * <p>
@@ -436,8 +438,10 @@
     public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010;
 
     /**
-     * Represents the event of opening a {@link android.widget.PopupWindow},
-     * {@link android.view.Menu}, {@link android.app.Dialog}, etc.
+     * Represents the event of a change to a visually distinct section of the user interface.
+     * These events should only be dispatched from {@link android.view.View}s that have
+     * accessibility pane titles, and replaces {@link #TYPE_WINDOW_CONTENT_CHANGED} for those
+     * sources. Details about the change are available from {@link #getContentChangeTypes()}.
      */
     public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020;
 
@@ -565,12 +569,30 @@
     public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
 
     /**
-     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
      * The node's pane title changed.
      */
     public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 0x00000008;
 
     /**
+     * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
+     * The node has a pane title, and either just appeared or just was assigned a title when it
+     * had none before.
+     */
+    public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 0x00000010;
+
+    /**
+     * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
+     * Can mean one of two slightly different things. The primary meaning is that the node has
+     * a pane title, and was removed from the node hierarchy. It will also be sent if the pane
+     * title is set to {@code null} after it contained a title.
+     * No source will be returned if the node is no longer on the screen. To make the change more
+     * clear for the user, the first entry in {@link #getText()} will return the value that would
+     * have been returned by {@code getSource().getPaneTitle()}.
+     */
+    public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 0x00000020;
+
+    /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window was added.
      */
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/Editor.java b/core/java/android/widget/Editor.java
index bddba07..247c806 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4006,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,
@@ -4014,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;
                 }
@@ -4032,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)));
             }
         }
 
@@ -4048,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;
         }
 
@@ -4626,7 +4627,7 @@
             return 0;
         }
 
-        protected final void showMagnifier() {
+        protected final void showMagnifier(@NonNull final MotionEvent event) {
             if (mMagnifier == null) {
                 return;
             }
@@ -4652,9 +4653,10 @@
 
             final Layout layout = mTextView.getLayout();
             final int lineNumber = layout.getLineForOffset(offset);
-            // Horizontally snap to character offset.
-            final float xPosInView = getHorizontal(mTextView.getLayout(), offset)
-                    + mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
+            // Horizontally move the magnifier smoothly.
+            final int[] textViewLocationOnScreen = new int[2];
+            mTextView.getLocationOnScreen(textViewLocationOnScreen);
+            final float xPosInView = event.getRawX() - textViewLocationOnScreen[0];
             // Vertically snap to middle of current line.
             final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber)
                     + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f
@@ -4849,11 +4851,11 @@
                 case MotionEvent.ACTION_DOWN:
                     mDownPositionX = ev.getRawX();
                     mDownPositionY = ev.getRawY();
-                    showMagnifier();
+                    showMagnifier(ev);
                     break;
 
                 case MotionEvent.ACTION_MOVE:
-                    showMagnifier();
+                    showMagnifier(ev);
                     break;
 
                 case MotionEvent.ACTION_UP:
@@ -5207,11 +5209,11 @@
                     // re-engages the handle.
                     mTouchWordDelta = 0.0f;
                     mPrevX = UNSET_X_VALUE;
-                    showMagnifier();
+                    showMagnifier(event);
                     break;
 
                 case MotionEvent.ACTION_MOVE:
-                    showMagnifier();
+                    showMagnifier(event);
                     break;
 
                 case MotionEvent.ACTION_UP:
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 26dfcc2..310b170 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -32,6 +32,7 @@
 import android.view.Surface;
 import android.view.SurfaceView;
 import android.view.View;
+import android.view.ViewParent;
 
 import com.android.internal.util.Preconditions;
 
@@ -44,6 +45,8 @@
     private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
     // The view to which this magnifier is attached.
     private final View mView;
+    // The coordinates of the view in the surface.
+    private final int[] mViewCoordinatesInSurface;
     // The window containing the magnifier.
     private final PopupWindow mWindow;
     // The center coordinates of the window containing the magnifier.
@@ -87,6 +90,8 @@
                 com.android.internal.R.dimen.magnifier_height);
         mZoomScale = context.getResources().getFloat(
                 com.android.internal.R.dimen.magnifier_zoom_scale);
+        // The view's surface coordinates will not be updated until the magnifier is first shown.
+        mViewCoordinatesInSurface = new int[2];
 
         mWindow = new PopupWindow(context);
         mWindow.setContentView(content);
@@ -120,9 +125,34 @@
         configureCoordinates(xPosInView, yPosInView);
 
         // Clamp startX value to avoid distorting the rendering of the magnifier content.
-        final int startX = Math.max(0, Math.min(
+        // For this, we compute:
+        // - zeroScrollXInSurface: this is the start x of mView, where this is not masked by a
+        //                         potential scrolling container. For example, if mView is a
+        //                         TextView contained in a HorizontalScrollView,
+        //                         mViewCoordinatesInSurface will reflect the surface position of
+        //                         the first text character, rather than the position of the first
+        //                         visible one. Therefore, we need to add back the amount of
+        //                         scrolling from the parent containers.
+        // - actualWidth: similarly, the width of a View will be larger than its actually visible
+        //                width when it is contained in a scrolling container. We need to use
+        //                the minimum width of a scrolling container which contains this view.
+        int zeroScrollXInSurface = mViewCoordinatesInSurface[0];
+        int actualWidth = mView.getWidth();
+        ViewParent viewParent = mView.getParent();
+        while (viewParent instanceof View) {
+            final View container = (View) viewParent;
+            if (container.canScrollHorizontally(-1 /* left scroll */)
+                    || container.canScrollHorizontally(1 /* right scroll */)) {
+                zeroScrollXInSurface += container.getScrollX();
+                actualWidth = Math.min(actualWidth, container.getWidth()
+                        - container.getPaddingLeft() - container.getPaddingRight());
+            }
+            viewParent = viewParent.getParent();
+        }
+
+        final int startX = Math.max(zeroScrollXInSurface, Math.min(
                 mCenterZoomCoords.x - mBitmap.getWidth() / 2,
-                mView.getWidth() - mBitmap.getWidth()));
+                zeroScrollXInSurface + actualWidth - mBitmap.getWidth()));
         final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
 
         if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) {
@@ -169,10 +199,9 @@
             posX = xPosInView;
             posY = yPosInView;
         } else {
-            final int[] coordinatesInSurface = new int[2];
-            mView.getLocationInSurface(coordinatesInSurface);
-            posX = xPosInView + coordinatesInSurface[0];
-            posY = yPosInView + coordinatesInSurface[1];
+            mView.getLocationInSurface(mViewCoordinatesInSurface);
+            posX = xPosInView + mViewCoordinatesInSurface[0];
+            posY = yPosInView + mViewCoordinatesInSurface[1];
         }
 
         mCenterZoomCoords.x = Math.round(posX);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 61477ad..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;
@@ -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);
         }
 
@@ -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();
diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java
new file mode 100644
index 0000000..310a7bb
--- /dev/null
+++ b/core/java/android/widget/VideoView2.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright 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.widget;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.media.AudioAttributes;
+import android.media.MediaPlayer;
+import android.media.update.ApiLoader;
+import android.media.update.VideoView2Provider;
+import android.media.update.ViewProvider;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+
+/**
+ * TODO PUBLIC API
+ * @hide
+ */
+public class VideoView2 extends FrameLayout {
+    @IntDef({
+            VIEW_TYPE_TEXTUREVIEW,
+            VIEW_TYPE_SURFACEVIEW
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ViewType {}
+    public static final int VIEW_TYPE_SURFACEVIEW = 1;
+    public static final int VIEW_TYPE_TEXTUREVIEW = 2;
+
+    private final VideoView2Provider mProvider;
+
+    /**
+     * @hide
+     */
+    public VideoView2(@NonNull Context context) {
+        this(context, null);
+    }
+
+    /**
+     * @hide
+     */
+    public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /**
+     * @hide
+     */
+    public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    /**
+     * @hide
+     */
+    public VideoView2(
+            @NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        mProvider = ApiLoader.getProvider(context).createVideoView2(this, new SuperProvider());
+    }
+
+    /**
+     * @hide
+     */
+    public VideoView2Provider getProvider() {
+        return mProvider;
+    }
+
+    /**
+     * @hide
+     */
+    public void start() {
+        mProvider.start_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void pause() {
+        mProvider.pause_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public int getDuration() {
+        return mProvider.getDuration_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public int getCurrentPosition() {
+        return mProvider.getCurrentPosition_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void seekTo(int msec) {
+        mProvider.seekTo_impl(msec);
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isPlaying() {
+        return mProvider.isPlaying_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public int getBufferPercentage() {
+        return mProvider.getBufferPercentage_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public int getAudioSessionId() {
+        return mProvider.getAudioSessionId_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void showSubtitle() {
+        mProvider.showSubtitle_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void hideSubtitle() {
+        mProvider.hideSubtitle_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void setAudioFocusRequest(int focusGain) {
+        mProvider.setAudioFocusRequest_impl(focusGain);
+    }
+
+    /**
+     * @hide
+     */
+    public void setAudioAttributes(@NonNull AudioAttributes attributes) {
+        mProvider.setAudioAttributes_impl(attributes);
+    }
+
+    /**
+     * @hide
+     */
+    public void setVideoPath(String path) {
+        mProvider.setVideoPath_impl(path);
+    }
+
+    /**
+     * @hide
+     */
+    public void setVideoURI(Uri uri) {
+        mProvider.setVideoURI_impl(uri);
+    }
+
+    /**
+     * @hide
+     */
+    public void setVideoURI(Uri uri, Map<String, String> headers) {
+        mProvider.setVideoURI_impl(uri, headers);
+    }
+
+    /**
+     * @hide
+     */
+    public void setMediaController2(MediaController2 controllerView) {
+        mProvider.setMediaController2_impl(controllerView);
+    }
+
+    /**
+     * @hide
+     */
+    public void setViewType(@ViewType int viewType) {
+        mProvider.setViewType_impl(viewType);
+    }
+
+    /**
+     * @hide
+     */
+    @ViewType
+    public int getViewType() {
+        return mProvider.getViewType_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void stopPlayback() {
+        mProvider.stopPlayback_impl();
+    }
+
+    /**
+     * @hide
+     */
+    public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) {
+        mProvider.setOnPreparedListener_impl(l);
+    }
+
+    /**
+     * @hide
+     */
+    public void setOnCompletionListener(MediaPlayer.OnCompletionListener l) {
+        mProvider.setOnCompletionListener_impl(l);
+    }
+
+    /**
+     * @hide
+     */
+    public void setOnErrorListener(MediaPlayer.OnErrorListener l) {
+        mProvider.setOnErrorListener_impl(l);
+    }
+
+    /**
+     * @hide
+     */
+    public void setOnInfoListener(MediaPlayer.OnInfoListener l) {
+        mProvider.setOnInfoListener_impl(l);
+    }
+
+    /**
+     * @hide
+     */
+    public void setOnViewTypeChangedListener(OnViewTypeChangedListener l) {
+        mProvider.setOnViewTypeChangedListener_impl(l);
+    }
+
+    /**
+     * @hide
+     */
+    public interface OnViewTypeChangedListener {
+        /**
+         * @hide
+         */
+        void onViewTypeChanged(@ViewType int viewType);
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return mProvider.getAccessibilityClassName_impl();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return mProvider.onTouchEvent_impl(ev);
+    }
+
+    @Override
+    public boolean onTrackballEvent(MotionEvent ev) {
+        return mProvider.onTrackballEvent_impl(ev);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return mProvider.onKeyDown_impl(keyCode, event);
+    }
+
+    @Override
+    public void onFinishInflate() {
+        mProvider.onFinishInflate_impl();
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        return mProvider.dispatchKeyEvent_impl(event);
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        mProvider.setEnabled_impl(enabled);
+    }
+
+    private class SuperProvider implements ViewProvider {
+        @Override
+        public void onAttachedToWindow_impl() {
+            VideoView2.super.onAttachedToWindow();
+        }
+
+        @Override
+        public void onDetachedFromWindow_impl() {
+            VideoView2.super.onDetachedFromWindow();
+        }
+
+        @Override
+        public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
+            VideoView2.super.onLayout(changed, left, top, right, bottom);
+        }
+
+        @Override
+        public void draw_impl(Canvas canvas) {
+            VideoView2.super.draw(canvas);
+        }
+
+        @Override
+        public CharSequence getAccessibilityClassName_impl() {
+            return VideoView2.super.getAccessibilityClassName();
+        }
+
+        @Override
+        public boolean onTouchEvent_impl(MotionEvent ev) {
+            return VideoView2.super.onTouchEvent(ev);
+        }
+
+        @Override
+        public boolean onTrackballEvent_impl(MotionEvent ev) {
+            return VideoView2.super.onTrackballEvent(ev);
+        }
+
+        @Override
+        public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
+            return VideoView2.super.onKeyDown(keyCode, event);
+        }
+
+        @Override
+        public void onFinishInflate_impl() {
+            VideoView2.super.onFinishInflate();
+        }
+
+        @Override
+        public boolean dispatchKeyEvent_impl(KeyEvent event) {
+            return VideoView2.super.dispatchKeyEvent(event);
+        }
+
+        @Override
+        public void setEnabled_impl(boolean enabled) {
+            VideoView2.super.setEnabled(enabled);
+        }
+    }
+}
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 439e5df..ac5dbc4 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -4062,8 +4062,6 @@
 
     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);
@@ -4074,9 +4072,8 @@
                             workSourceName != null ? workSourceName : packageName);
                     pkg.noteWakeupAlarmLocked(tag);
                 }
-                uids[0] = workSource.get(i);
-                tags[0] = workSource.getName(i);
-                StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uids, tags, tag);
+                StatsLog.write_non_chained(StatsLog.WAKEUP_ALARM_OCCURRED, workSource.get(i),
+                        workSource.getName(i), tag);
             }
 
             ArrayList<WorkChain> workChains = workSource.getWorkChains();
@@ -4097,9 +4094,7 @@
                 BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName);
                 pkg.noteWakeupAlarmLocked(tag);
             }
-            uids[0] = uid;
-            tags[0] = null;
-            StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uids, tags, tag);
+            StatsLog.write_non_chained(StatsLog.WAKEUP_ALARM_OCCURRED, uid, null, tag);
         }
     }
 
@@ -4224,9 +4219,8 @@
                 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);
+                StatsLog.write_non_chained(StatsLog.WAKELOCK_STATE_CHANGED, uid, null, type, name,
+                        1);
             }
         }
     }
@@ -4268,9 +4262,8 @@
                 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);
+                StatsLog.write_non_chained(StatsLog.WAKELOCK_STATE_CHANGED, uid, null, type, name,
+                        0);
             }
         }
     }
@@ -4364,10 +4357,8 @@
     }
 
     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);
+        StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+                uid, null, name, historyName, 1);
 
         uid = mapUid(uid);
         noteLongPartialWakeLockStartInternal(name, historyName, uid);
@@ -4376,15 +4367,11 @@
     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);
-            uids[0] = workSource.get(i);
-            tags[0] = workSource.getName(i);
-            StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uids, tags, name,
-                    historyName, 1);
+            StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+                    workSource.get(i), workSource.getName(i), name, historyName, 1);
         }
 
         final ArrayList<WorkChain> workChains = workSource.getWorkChains();
@@ -4415,10 +4402,8 @@
     }
 
     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);
+        StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+                uid, null, name, historyName, 0);
 
         uid = mapUid(uid);
         noteLongPartialWakeLockFinishInternal(name, historyName, uid);
@@ -4427,15 +4412,11 @@
     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);
-            uids[0] = workSource.get(i);
-            tags[0] = workSource.getName(i);
-            StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
-                    uids, tags, name, historyName, 0);
+            StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+                    workSource.get(i), workSource.getName(i), name, historyName, 0);
         }
 
         final ArrayList<WorkChain> workChains = workSource.getWorkChains();
@@ -5420,11 +5401,10 @@
                         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);
+            StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, uid, null, 1);
             if (isUnoptimized) {
-                StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uids, tags, 1);
+                StatsLog.write_non_chained(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, null,
+                        1);
             }
         }
 
@@ -5470,11 +5450,10 @@
                         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);
+            StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, uid, null, 0);
             if (isUnoptimized) {
-                StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uids, tags, 0);
+                StatsLog.write_non_chained(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, null,
+                        0);
             }
         }
 
@@ -5547,14 +5526,11 @@
 
     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);
-            uids[0] = ws.get(i);
-            tags[0] = ws.getName(i);
-            StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, uids, tags, numNewResults);
+            StatsLog.write_non_chained(StatsLog.BLE_SCAN_RESULT_RECEIVED, ws.get(i), ws.getName(i),
+                    numNewResults);
         }
 
         final List<WorkChain> workChains = ws.getWorkChains();
@@ -5879,14 +5855,10 @@
 
     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);
-            uids[0] = ws.get(i);
-            tags[0] = ws.getName(i);
-            StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uids, tags, 1);
+            StatsLog.write_non_chained(StatsLog.WIFI_LOCK_STATE_CHANGED, ws.get(i), ws.getName(i), 1);
         }
 
         final List<WorkChain> workChains = ws.getWorkChains();
@@ -5903,14 +5875,10 @@
 
     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);
-            uids[0] = ws.get(i);
-            tags[0] = ws.getName(i);
-            StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uids, tags, 0);
+            StatsLog.write_non_chained(StatsLog.WIFI_LOCK_STATE_CHANGED, ws.get(i), ws.getName(i), 0);
         }
 
         final List<WorkChain> workChains = ws.getWorkChains();
@@ -5927,14 +5895,11 @@
 
     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);
-            uids[0] = ws.get(i);
-            tags[0] = ws.getName(i);
-            StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uids, tags, 1);
+            StatsLog.write_non_chained(StatsLog.WIFI_SCAN_STATE_CHANGED, ws.get(i), ws.getName(i),
+                    1);
         }
 
         final List<WorkChain> workChains = ws.getWorkChains();
@@ -5951,14 +5916,11 @@
 
     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);
-            uids[0] = ws.get(i);
-            tags[0] = ws.getName(i);
-            StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uids, tags, 0);
+            StatsLog.write_non_chained(StatsLog.WIFI_SCAN_STATE_CHANGED, ws.get(i), ws.getName(i),
+                    0);
         }
 
         final List<WorkChain> workChains = ws.getWorkChains();
@@ -6934,18 +6896,14 @@
 
         public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) {
             createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
-            final int[] uids = new int[] { getUid() };
-            final String[] tags = new String[] { null };
-            StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 1);
+            StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, 1);
         }
 
         public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) {
             if (mAudioTurnedOnTimer != null) {
                 mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
                 if (!mAudioTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
-                    final int[] uids = new int[] { getUid() };
-                    final String[] tags = new String[] { null };
-                    StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 0);
+                    StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, 0);
                 }
             }
         }
@@ -6953,9 +6911,7 @@
         public void noteResetAudioLocked(long elapsedRealtimeMs) {
             if (mAudioTurnedOnTimer != null) {
                 mAudioTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
-                final int[] uids = new int[] { getUid() };
-                final String[] tags = new String[] { null };
-                StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 0);
+                StatsLog.write_non_chained(StatsLog.AUDIO_STATE_CHANGED, getUid(), null, 0);
             }
         }
 
@@ -6969,18 +6925,15 @@
 
         public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) {
             createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
-            final int[] uids = new int[] { getUid() };
-            final String[] tags = new String[] { null };
-            StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 1);
+            StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), null, 1);
         }
 
         public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) {
             if (mVideoTurnedOnTimer != null) {
                 mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
                 if (!mVideoTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
-                    final int[] uids = new int[] { getUid() };
-                    final String[] tags = new String[] { null };
-                    StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 0);
+                    StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(),
+                            null, 0);
                 }
             }
         }
@@ -6988,9 +6941,8 @@
         public void noteResetVideoLocked(long elapsedRealtimeMs) {
             if (mVideoTurnedOnTimer != null) {
                 mVideoTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
-                final int[] uids = new int[] { getUid() };
-                final String[] tags = new String[] { null };
-                StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 0);
+                StatsLog.write_non_chained(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), null,
+                        0);
             }
         }
 
@@ -7004,18 +6956,15 @@
 
         public void noteFlashlightTurnedOnLocked(long elapsedRealtimeMs) {
             createFlashlightTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
-            final int[] uids = new int[] { getUid() };
-            final String[] tags = new String[] { null };
-            StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 1);
+            StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null,1);
         }
 
         public void noteFlashlightTurnedOffLocked(long elapsedRealtimeMs) {
             if (mFlashlightTurnedOnTimer != null) {
                 mFlashlightTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
                 if (!mFlashlightTurnedOnTimer.isRunningLocked()) {
-                    final int[] uids = new int[] { getUid() };
-                    final String[] tags = new String[] { null };
-                    StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 0);
+                    StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null,
+                            0);
                 }
             }
         }
@@ -7023,9 +6972,7 @@
         public void noteResetFlashlightLocked(long elapsedRealtimeMs) {
             if (mFlashlightTurnedOnTimer != null) {
                 mFlashlightTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
-                final int[] uids = new int[] { getUid() };
-                final String[] tags = new String[] { null };
-                StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 0);
+                StatsLog.write_non_chained(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), null, 0);
             }
         }
 
@@ -7039,18 +6986,14 @@
 
         public void noteCameraTurnedOnLocked(long elapsedRealtimeMs) {
             createCameraTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
-            final int[] uids = new int[] { getUid() };
-            final String[] tags = new String[] { null };
-            StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 1);
+            StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, 1);
         }
 
         public void noteCameraTurnedOffLocked(long elapsedRealtimeMs) {
             if (mCameraTurnedOnTimer != null) {
                 mCameraTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
                 if (!mCameraTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
-                    final int[] uids = new int[] { getUid() };
-                    final String[] tags = new String[] { null };
-                    StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 0);
+                    StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, 0);
                 }
             }
         }
@@ -7058,9 +7001,7 @@
         public void noteResetCameraLocked(long elapsedRealtimeMs) {
             if (mCameraTurnedOnTimer != null) {
                 mCameraTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
-                final int[] uids = new int[] { getUid() };
-                final String[] tags = new String[] { null };
-                StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 0);
+                StatsLog.write_non_chained(StatsLog.CAMERA_STATE_CHANGED, getUid(), null, 0);
             }
         }
 
@@ -9622,9 +9563,7 @@
             DualTimer t = mSyncStats.startObject(name);
             if (t != null) {
                 t.startRunningLocked(elapsedRealtimeMs);
-                final int[] uids = new int[] { getUid() };
-                final String[] tags = new String[] { null };
-                StatsLog.write(StatsLog.SYNC_STATE_CHANGED, uids, tags, name, 1);
+                StatsLog.write_non_chained(StatsLog.SYNC_STATE_CHANGED, getUid(), null, name, 1);
             }
         }
 
@@ -9633,9 +9572,7 @@
             if (t != null) {
                 t.stopRunningLocked(elapsedRealtimeMs);
                 if (!t.isRunningLocked()) { // only tell statsd if truly stopped
-                    final int[] uids = new int[] { getUid() };
-                    final String[] tags = new String[] { null };
-                    StatsLog.write(StatsLog.SYNC_STATE_CHANGED, uids, tags, name, 0);
+                    StatsLog.write_non_chained(StatsLog.SYNC_STATE_CHANGED, getUid(), null, name, 0);
                 }
             }
         }
@@ -9644,9 +9581,8 @@
             DualTimer t = mJobStats.startObject(name);
             if (t != null) {
                 t.startRunningLocked(elapsedRealtimeMs);
-                final int[] uids = new int[] { getUid() };
-                final String[] tags = new String[] { null };
-                StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, uids, tags, name, 1);
+                StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), null,
+                        name, 1);
             }
         }
 
@@ -9655,9 +9591,8 @@
             if (t != null) {
                 t.stopRunningLocked(elapsedRealtimeMs);
                 if (!t.isRunningLocked()) { // only tell statsd if truly stopped
-                    final int[] uids = new int[] { getUid() };
-                    final String[] tags = new String[] { null };
-                    StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, uids, tags, name, 0);
+                    StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), null,
+                            name, 0);
                 }
             }
             if (mBsi.mOnBatteryTimeBase.isRunning()) {
@@ -9768,12 +9703,11 @@
         public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
             DualTimer t = getSensorTimerLocked(sensor, /* create= */ true);
             t.startRunningLocked(elapsedRealtimeMs);
-            final int[] uids = new int[] { getUid() };
-            final String[] tags = new String[] { null };
             if (sensor == Sensor.GPS) {
-                StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, uids, tags, 1);
+                StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), null, 1);
             } else {
-                StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, uids, tags, sensor, 1);
+                StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null, sensor,
+                        1);
             }
         }
 
@@ -9783,13 +9717,12 @@
             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.
-                    final int[] uids = new int[] { getUid() };
-                    final String[] tags = new String[] { null };
                     if (sensor == Sensor.GPS) {
-                        StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, uids, tags, 0);
+                        StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), null,
+                                0);
                     } else {
-                        StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, uids, tags, sensor, 0);
+                        StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null,
+                                sensor, 0);
                     }
                 }
             }
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index c5fe4cb..f814ba9 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -572,10 +572,12 @@
                 final String seInfo = null;
                 final String classLoaderContext =
                         getSystemServerClassLoaderContext(classPathForElement);
+                final int targetSdkVersion = 0;  // SystemServer targets the system's SDK version
                 try {
                     installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName,
                             instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter,
-                            uuid, classLoaderContext, seInfo, false /* downgrade */);
+                            uuid, classLoaderContext, seInfo, false /* downgrade */,
+                            targetSdkVersion);
                 } catch (RemoteException | ServiceSpecificException e) {
                     // Ignore (but log), we need this on the classpath for fallback mode.
                     Log.w(TAG, "Failed compiling classpath element for system server: "
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/policy/KeyguardDismissCallback.java b/core/java/com/android/internal/policy/KeyguardDismissCallback.java
new file mode 100644
index 0000000..38337ec
--- /dev/null
+++ b/core/java/com/android/internal/policy/KeyguardDismissCallback.java
@@ -0,0 +1,41 @@
+/*
+ * 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.policy;
+
+import android.os.RemoteException;
+import com.android.internal.policy.IKeyguardDismissCallback;
+
+/**
+ * @hide
+ */
+public class KeyguardDismissCallback extends IKeyguardDismissCallback.Stub {
+
+    @Override
+    public void onDismissError() throws RemoteException {
+        // To be overidden
+    }
+
+    @Override
+    public void onDismissSucceeded() throws RemoteException {
+        // To be overidden
+    }
+
+    @Override
+    public void onDismissCancelled() throws RemoteException {
+        // To be overidden
+    }
+}
diff --git a/core/java/com/android/internal/print/DualDumpOutputStream.java b/core/java/com/android/internal/print/DualDumpOutputStream.java
new file mode 100644
index 0000000..f7826cc
--- /dev/null
+++ b/core/java/com/android/internal/print/DualDumpOutputStream.java
@@ -0,0 +1,265 @@
+/*
+ * 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.print;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+
+/**
+ * Dump either to a proto or a print writer using the same interface.
+ *
+ * <p>This mirrors the interface of {@link ProtoOutputStream}.
+ */
+public class DualDumpOutputStream {
+    // When writing to a proto, the proto
+    private final @Nullable ProtoOutputStream mProtoStream;
+
+    // When printing in clear text, the writer
+    private final @Nullable IndentingPrintWriter mIpw;
+    // Temporary storage of data when printing to mIpw
+    private final LinkedList<DumpObject> mDumpObjects = new LinkedList<>();
+
+    private static abstract class DumpAble {
+        final String name;
+
+        private DumpAble(String name) {
+            this.name = name;
+        }
+
+        abstract void print(IndentingPrintWriter ipw, boolean printName);
+    }
+
+    private static class DumpObject extends DumpAble {
+        private final LinkedHashMap<String, ArrayList<DumpAble>> mSubObjects = new LinkedHashMap<>();
+
+        private DumpObject(String name) {
+            super(name);
+        }
+
+        @Override
+        void print(IndentingPrintWriter ipw, boolean printName) {
+            if (printName) {
+                ipw.println(name + "={");
+            } else {
+                ipw.println("{");
+            }
+            ipw.increaseIndent();
+
+            for (ArrayList<DumpAble> subObject: mSubObjects.values()) {
+                int numDumpables = subObject.size();
+
+                if (numDumpables == 1) {
+                    subObject.get(0).print(ipw, true);
+                } else {
+                    ipw.println(subObject.get(0).name + "=[");
+                    ipw.increaseIndent();
+
+                    for (int i = 0; i < numDumpables; i++) {
+                        subObject.get(i).print(ipw, false);
+                    }
+
+                    ipw.decreaseIndent();
+                    ipw.println("]");
+                }
+            }
+
+            ipw.decreaseIndent();
+            ipw.println("}");
+        }
+
+        /**
+         * Add new field / subobject to this object.
+         *
+         * <p>If a name is added twice, they will be printed as a array
+         *
+         * @param fieldName name of the field added
+         * @param d The dumpable to add
+         */
+        public void add(String fieldName, DumpAble d) {
+            ArrayList<DumpAble> l = mSubObjects.get(fieldName);
+
+            if (l == null) {
+                l = new ArrayList<>(1);
+                mSubObjects.put(fieldName, l);
+            }
+
+            l.add(d);
+        }
+    }
+
+    private static class DumpField extends DumpAble {
+        private final String mValue;
+
+        private DumpField(String name, String value) {
+            super(name);
+            this.mValue = value;
+        }
+
+        @Override
+        void print(IndentingPrintWriter ipw, boolean printName) {
+            if (printName) {
+                ipw.println(name + "=" + mValue);
+            } else {
+                ipw.println(mValue);
+            }
+        }
+    }
+
+
+    /**
+     * Create a new DualDumpOutputStream. Only one output should be set.
+     *
+     * @param proto If dumping to proto the {@link ProtoOutputStream}
+     * @param ipw If dumping to a print writer, the {@link IndentingPrintWriter}
+     */
+    public DualDumpOutputStream(@Nullable ProtoOutputStream proto,
+            @Nullable IndentingPrintWriter ipw) {
+        Preconditions.checkArgument((proto == null) != (ipw == null));
+
+        mProtoStream = proto;
+        mIpw = ipw;
+
+        if (!isProto()) {
+            // Add root object
+            mDumpObjects.add(new DumpObject(null));
+        }
+    }
+
+    public void write(@NonNull String fieldName, long fieldId, double val) {
+        if (mProtoStream != null) {
+            mProtoStream.write(fieldId, val);
+        } else {
+            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
+        }
+    }
+
+    public void write(@NonNull String fieldName, long fieldId, boolean val) {
+        if (mProtoStream != null) {
+            mProtoStream.write(fieldId, val);
+        } else {
+            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
+        }
+    }
+
+    public void write(@NonNull String fieldName, long fieldId, int val) {
+        if (mProtoStream != null) {
+            mProtoStream.write(fieldId, val);
+        } else {
+            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
+        }
+    }
+
+    public void write(@NonNull String fieldName, long fieldId, float val) {
+        if (mProtoStream != null) {
+            mProtoStream.write(fieldId, val);
+        } else {
+            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
+        }
+    }
+
+    public void write(@NonNull String fieldName, long fieldId, byte[] val) {
+        if (mProtoStream != null) {
+            mProtoStream.write(fieldId, val);
+        } else {
+            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, Arrays.toString(val)));
+        }
+    }
+
+    public void write(@NonNull String fieldName, long fieldId, long val) {
+        if (mProtoStream != null) {
+            mProtoStream.write(fieldId, val);
+        } else {
+            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
+        }
+    }
+
+    public void write(@NonNull String fieldName, long fieldId, @Nullable String val) {
+        if (mProtoStream != null) {
+            mProtoStream.write(fieldId, val);
+        } else {
+            mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val)));
+        }
+    }
+
+    public long start(@NonNull String fieldName, long fieldId) {
+        if (mProtoStream != null) {
+            return mProtoStream.start(fieldId);
+        } else {
+            DumpObject d = new DumpObject(fieldName);
+            mDumpObjects.getLast().add(fieldName, d);
+            mDumpObjects.addLast(d);
+            return 0;
+        }
+    }
+
+    public void end(long token) {
+        if (mProtoStream != null) {
+            mProtoStream.end(token);
+        } else {
+            mDumpObjects.removeLast();
+        }
+    }
+
+    public void flush() {
+        if (mProtoStream != null) {
+            mProtoStream.flush();
+        } else {
+            if (mDumpObjects.size() == 1) {
+                mDumpObjects.getFirst().print(mIpw, false);
+
+                // Reset root object
+                mDumpObjects.clear();
+                mDumpObjects.add(new DumpObject(null));
+            }
+
+            mIpw.flush();
+        }
+    }
+
+    /**
+     * Add a dump from a different service into this dump.
+     *
+     * <p>Only for clear text dump. For proto dump use {@link #write(String, long, byte[])}.
+     *
+     * @param fieldName The name of the field
+     * @param nestedState The state of the dump
+     */
+    public void writeNested(@NonNull String fieldName, byte[] nestedState) {
+        Preconditions.checkNotNull(mIpw);
+
+        mDumpObjects.getLast().add(fieldName,
+                new DumpField(fieldName, (new String(nestedState, StandardCharsets.UTF_8)).trim()));
+    }
+
+    /**
+     * @return {@code true} iff we are dumping to a proto
+     */
+    public boolean isProto() {
+        return mProtoStream != null;
+    }
+}
diff --git a/core/java/com/android/internal/print/DumpUtils.java b/core/java/com/android/internal/print/DumpUtils.java
index 28c7fc2..3192d5c 100644
--- a/core/java/com/android/internal/print/DumpUtils.java
+++ b/core/java/com/android/internal/print/DumpUtils.java
@@ -39,7 +39,6 @@
 import android.service.print.PrinterIdProto;
 import android.service.print.PrinterInfoProto;
 import android.service.print.ResolutionProto;
-import android.util.proto.ProtoOutputStream;
 
 /**
  * Utilities for dumping print related proto buffer
@@ -49,13 +48,14 @@
      * Write a string to a proto if the string is not {@code null}.
      *
      * @param proto The proto to write to
+     * @param idName Clear text name of the proto-id
      * @param id The proto-id of the string
      * @param string The string to write
      */
-    public static void writeStringIfNotNull(@NonNull ProtoOutputStream proto, long id,
-            @Nullable String string) {
+    public static void writeStringIfNotNull(@NonNull DualDumpOutputStream proto, String idName,
+            long id, @Nullable String string) {
         if (string != null) {
-            proto.write(id, string);
+            proto.write(idName, id, string);
         }
     }
 
@@ -63,14 +63,15 @@
      * Write a {@link ComponentName} to a proto.
      *
      * @param proto The proto to write to
+     * @param idName Clear text name of the proto-id
      * @param id The proto-id of the component name
      * @param component The component name to write
      */
-    public static void writeComponentName(@NonNull ProtoOutputStream proto, long id,
-            @NonNull ComponentName component) {
-        long token = proto.start(id);
-        proto.write(ComponentNameProto.PACKAGE_NAME, component.getPackageName());
-        proto.write(ComponentNameProto.CLASS_NAME, component.getClassName());
+    public static void writeComponentName(@NonNull DualDumpOutputStream proto, String idName,
+            long id, @NonNull ComponentName component) {
+        long token = proto.start(idName, id);
+        proto.write("package_name", ComponentNameProto.PACKAGE_NAME, component.getPackageName());
+        proto.write("class_name", ComponentNameProto.CLASS_NAME, component.getClassName());
         proto.end(token);
     }
 
@@ -78,14 +79,16 @@
      * Write a {@link PrinterId} to a proto.
      *
      * @param proto The proto to write to
+     * @param idName Clear text name of the proto-id
      * @param id The proto-id of the component name
      * @param printerId The printer id to write
      */
-    public static void writePrinterId(@NonNull ProtoOutputStream proto, long id,
+    public static void writePrinterId(@NonNull DualDumpOutputStream proto, String idName, long id,
             @NonNull PrinterId printerId) {
-        long token = proto.start(id);
-        writeComponentName(proto, PrinterIdProto.SERVICE_NAME, printerId.getServiceName());
-        proto.write(PrinterIdProto.LOCAL_ID, printerId.getLocalId());
+        long token = proto.start(idName, id);
+        writeComponentName(proto, "service_name", PrinterIdProto.SERVICE_NAME,
+                printerId.getServiceName());
+        proto.write("local_id", PrinterIdProto.LOCAL_ID, printerId.getLocalId());
         proto.end(token);
     }
 
@@ -93,71 +96,76 @@
      * Write a {@link PrinterCapabilitiesInfo} to a proto.
      *
      * @param proto The proto to write to
+     * @param idName Clear text name of the proto-id
      * @param id The proto-id of the component name
      * @param cap The capabilities to write
      */
     public static void writePrinterCapabilities(@NonNull Context context,
-            @NonNull ProtoOutputStream proto, long id, @NonNull PrinterCapabilitiesInfo cap) {
-        long token = proto.start(id);
-        writeMargins(proto, PrinterCapabilitiesProto.MIN_MARGINS, cap.getMinMargins());
+            @NonNull DualDumpOutputStream proto, String idName, long id,
+            @NonNull PrinterCapabilitiesInfo cap) {
+        long token = proto.start(idName, id);
+        writeMargins(proto, "min_margins", PrinterCapabilitiesProto.MIN_MARGINS,
+                cap.getMinMargins());
 
         int numMediaSizes = cap.getMediaSizes().size();
         for (int i = 0; i < numMediaSizes; i++) {
-            writeMediaSize(context, proto, PrinterCapabilitiesProto.MEDIA_SIZES,
+            writeMediaSize(context, proto, "media_sizes", PrinterCapabilitiesProto.MEDIA_SIZES,
                     cap.getMediaSizes().get(i));
         }
 
         int numResolutions = cap.getResolutions().size();
         for (int i = 0; i < numResolutions; i++) {
-            writeResolution(proto, PrinterCapabilitiesProto.RESOLUTIONS,
+            writeResolution(proto, "resolutions", PrinterCapabilitiesProto.RESOLUTIONS,
                     cap.getResolutions().get(i));
         }
 
         if ((cap.getColorModes() & PrintAttributes.COLOR_MODE_MONOCHROME) != 0) {
-            proto.write(PrinterCapabilitiesProto.COLOR_MODES,
+            proto.write("color_modes", PrinterCapabilitiesProto.COLOR_MODES,
                     PrintAttributesProto.COLOR_MODE_MONOCHROME);
         }
         if ((cap.getColorModes() & PrintAttributes.COLOR_MODE_COLOR) != 0) {
-            proto.write(PrinterCapabilitiesProto.COLOR_MODES,
+            proto.write("color_modes", PrinterCapabilitiesProto.COLOR_MODES,
                     PrintAttributesProto.COLOR_MODE_COLOR);
         }
 
         if ((cap.getDuplexModes() & PrintAttributes.DUPLEX_MODE_NONE) != 0) {
-            proto.write(PrinterCapabilitiesProto.DUPLEX_MODES,
+            proto.write("duplex_modes", PrinterCapabilitiesProto.DUPLEX_MODES,
                     PrintAttributesProto.DUPLEX_MODE_NONE);
         }
         if ((cap.getDuplexModes() & PrintAttributes.DUPLEX_MODE_LONG_EDGE) != 0) {
-            proto.write(PrinterCapabilitiesProto.DUPLEX_MODES,
+            proto.write("duplex_modes", PrinterCapabilitiesProto.DUPLEX_MODES,
                     PrintAttributesProto.DUPLEX_MODE_LONG_EDGE);
         }
         if ((cap.getDuplexModes() & PrintAttributes.DUPLEX_MODE_SHORT_EDGE) != 0) {
-            proto.write(PrinterCapabilitiesProto.DUPLEX_MODES,
+            proto.write("duplex_modes", PrinterCapabilitiesProto.DUPLEX_MODES,
                     PrintAttributesProto.DUPLEX_MODE_SHORT_EDGE);
         }
 
         proto.end(token);
     }
 
-
     /**
      * Write a {@link PrinterInfo} to a proto.
      *
      * @param context The context used to resolve resources
      * @param proto The proto to write to
+     * @param idName Clear text name of the proto-id
      * @param id The proto-id of the component name
      * @param info The printer info to write
      */
-    public static void writePrinterInfo(@NonNull Context context, @NonNull ProtoOutputStream proto,
-            long id, @NonNull PrinterInfo info) {
-        long token = proto.start(id);
-        writePrinterId(proto, PrinterInfoProto.ID, info.getId());
-        proto.write(PrinterInfoProto.NAME, info.getName());
-        proto.write(PrinterInfoProto.STATUS, info.getStatus());
-        proto.write(PrinterInfoProto.DESCRIPTION, info.getDescription());
+    public static void writePrinterInfo(@NonNull Context context,
+            @NonNull DualDumpOutputStream proto, String idName, long id,
+            @NonNull PrinterInfo info) {
+        long token = proto.start(idName, id);
+        writePrinterId(proto, "id", PrinterInfoProto.ID, info.getId());
+        proto.write("name", PrinterInfoProto.NAME, info.getName());
+        proto.write("status", PrinterInfoProto.STATUS, info.getStatus());
+        proto.write("description", PrinterInfoProto.DESCRIPTION, info.getDescription());
 
         PrinterCapabilitiesInfo cap = info.getCapabilities();
         if (cap != null) {
-            writePrinterCapabilities(context, proto, PrinterInfoProto.CAPABILITIES, cap);
+            writePrinterCapabilities(context, proto, "capabilities", PrinterInfoProto.CAPABILITIES,
+                    cap);
         }
 
         proto.end(token);
@@ -168,16 +176,17 @@
      *
      * @param context The context used to resolve resources
      * @param proto The proto to write to
+     * @param idName Clear text name of the proto-id
      * @param id The proto-id of the component name
      * @param mediaSize The media size to write
      */
-    public static void writeMediaSize(@NonNull Context context, @NonNull ProtoOutputStream proto,
-            long id, @NonNull PrintAttributes.MediaSize mediaSize) {
-        long token = proto.start(id);
-        proto.write(MediaSizeProto.ID, mediaSize.getId());
-        proto.write(MediaSizeProto.LABEL, mediaSize.getLabel(context.getPackageManager()));
-        proto.write(MediaSizeProto.HEIGHT_MILS, mediaSize.getHeightMils());
-        proto.write(MediaSizeProto.WIDTH_MILS, mediaSize.getWidthMils());
+    public static void writeMediaSize(@NonNull Context context, @NonNull DualDumpOutputStream proto,
+            String idName, long id, @NonNull PrintAttributes.MediaSize mediaSize) {
+        long token = proto.start(idName, id);
+        proto.write("id", MediaSizeProto.ID, mediaSize.getId());
+        proto.write("label", MediaSizeProto.LABEL, mediaSize.getLabel(context.getPackageManager()));
+        proto.write("height_mils", MediaSizeProto.HEIGHT_MILS, mediaSize.getHeightMils());
+        proto.write("width_mils", MediaSizeProto.WIDTH_MILS, mediaSize.getWidthMils());
         proto.end(token);
     }
 
@@ -185,16 +194,17 @@
      * Write a {@link PrintAttributes.Resolution} to a proto.
      *
      * @param proto The proto to write to
+     * @param idName Clear text name of the proto-id
      * @param id The proto-id of the component name
      * @param res The resolution to write
      */
-    public static void writeResolution(@NonNull ProtoOutputStream proto, long id,
+    public static void writeResolution(@NonNull DualDumpOutputStream proto, String idName, long id,
             @NonNull PrintAttributes.Resolution res) {
-        long token = proto.start(id);
-        proto.write(ResolutionProto.ID, res.getId());
-        proto.write(ResolutionProto.LABEL, res.getLabel());
-        proto.write(ResolutionProto.HORIZONTAL_DPI, res.getHorizontalDpi());
-        proto.write(ResolutionProto.VERTICAL_DPI, res.getVerticalDpi());
+        long token = proto.start(idName, id);
+        proto.write("id", ResolutionProto.ID, res.getId());
+        proto.write("label", ResolutionProto.LABEL, res.getLabel());
+        proto.write("horizontal_DPI", ResolutionProto.HORIZONTAL_DPI, res.getHorizontalDpi());
+        proto.write("veritical_DPI", ResolutionProto.VERTICAL_DPI, res.getVerticalDpi());
         proto.end(token);
     }
 
@@ -202,16 +212,17 @@
      * Write a {@link PrintAttributes.Margins} to a proto.
      *
      * @param proto The proto to write to
+     * @param idName Clear text name of the proto-id
      * @param id The proto-id of the component name
      * @param margins The margins to write
      */
-    public static void writeMargins(@NonNull ProtoOutputStream proto, long id,
+    public static void writeMargins(@NonNull DualDumpOutputStream proto, String idName, long id,
             @NonNull PrintAttributes.Margins margins) {
-        long token = proto.start(id);
-        proto.write(MarginsProto.TOP_MILS, margins.getTopMils());
-        proto.write(MarginsProto.LEFT_MILS, margins.getLeftMils());
-        proto.write(MarginsProto.RIGHT_MILS, margins.getRightMils());
-        proto.write(MarginsProto.BOTTOM_MILS, margins.getBottomMils());
+        long token = proto.start(idName, id);
+        proto.write("top_mils", MarginsProto.TOP_MILS, margins.getTopMils());
+        proto.write("left_mils", MarginsProto.LEFT_MILS, margins.getLeftMils());
+        proto.write("right_mils", MarginsProto.RIGHT_MILS, margins.getRightMils());
+        proto.write("bottom_mils", MarginsProto.BOTTOM_MILS, margins.getBottomMils());
         proto.end(token);
     }
 
@@ -220,32 +231,34 @@
      *
      * @param context The context used to resolve resources
      * @param proto The proto to write to
+     * @param idName Clear text name of the proto-id
      * @param id The proto-id of the component name
      * @param attributes The attributes to write
      */
     public static void writePrintAttributes(@NonNull Context context,
-            @NonNull ProtoOutputStream proto, long id, @NonNull PrintAttributes attributes) {
-        long token = proto.start(id);
+            @NonNull DualDumpOutputStream proto, String idName, long id,
+            @NonNull PrintAttributes attributes) {
+        long token = proto.start(idName, id);
 
         PrintAttributes.MediaSize mediaSize = attributes.getMediaSize();
         if (mediaSize != null) {
-            writeMediaSize(context, proto, PrintAttributesProto.MEDIA_SIZE, mediaSize);
+            writeMediaSize(context, proto, "media_size", PrintAttributesProto.MEDIA_SIZE, mediaSize);
         }
 
-        proto.write(PrintAttributesProto.IS_PORTRAIT, attributes.isPortrait());
+        proto.write("is_portrait", PrintAttributesProto.IS_PORTRAIT, attributes.isPortrait());
 
         PrintAttributes.Resolution res = attributes.getResolution();
         if (res != null) {
-            writeResolution(proto, PrintAttributesProto.RESOLUTION, res);
+            writeResolution(proto, "resolution", PrintAttributesProto.RESOLUTION, res);
         }
 
         PrintAttributes.Margins minMargins = attributes.getMinMargins();
         if (minMargins != null) {
-            writeMargins(proto, PrintAttributesProto.MIN_MARGINS, minMargins);
+            writeMargins(proto, "min_margings", PrintAttributesProto.MIN_MARGINS, minMargins);
         }
 
-        proto.write(PrintAttributesProto.COLOR_MODE, attributes.getColorMode());
-        proto.write(PrintAttributesProto.DUPLEX_MODE, attributes.getDuplexMode());
+        proto.write("color_mode", PrintAttributesProto.COLOR_MODE, attributes.getColorMode());
+        proto.write("duplex_mode", PrintAttributesProto.DUPLEX_MODE, attributes.getDuplexMode());
         proto.end(token);
     }
 
@@ -253,21 +266,22 @@
      * Write a {@link PrintDocumentInfo} to a proto.
      *
      * @param proto The proto to write to
+     * @param idName Clear text name of the proto-id
      * @param id The proto-id of the component name
      * @param info The info to write
      */
-    public static void writePrintDocumentInfo(@NonNull ProtoOutputStream proto, long id,
-            @NonNull PrintDocumentInfo info) {
-        long token = proto.start(id);
-        proto.write(PrintDocumentInfoProto.NAME, info.getName());
+    public static void writePrintDocumentInfo(@NonNull DualDumpOutputStream proto, String idName,
+            long id, @NonNull PrintDocumentInfo info) {
+        long token = proto.start(idName, id);
+        proto.write("name", PrintDocumentInfoProto.NAME, info.getName());
 
         int pageCount = info.getPageCount();
         if (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
-            proto.write(PrintDocumentInfoProto.PAGE_COUNT, pageCount);
+            proto.write("page_count", PrintDocumentInfoProto.PAGE_COUNT, pageCount);
         }
 
-        proto.write(PrintDocumentInfoProto.CONTENT_TYPE, info.getContentType());
-        proto.write(PrintDocumentInfoProto.DATA_SIZE, info.getDataSize());
+        proto.write("content_type", PrintDocumentInfoProto.CONTENT_TYPE, info.getContentType());
+        proto.write("data_size", PrintDocumentInfoProto.DATA_SIZE, info.getDataSize());
         proto.end(token);
     }
 
@@ -275,14 +289,15 @@
      * Write a {@link PageRange} to a proto.
      *
      * @param proto The proto to write to
+     * @param idName Clear text name of the proto-id
      * @param id The proto-id of the component name
      * @param range The range to write
      */
-    public static void writePageRange(@NonNull ProtoOutputStream proto, long id,
+    public static void writePageRange(@NonNull DualDumpOutputStream proto, String idName, long id,
             @NonNull PageRange range) {
-        long token = proto.start(id);
-        proto.write(PageRangeProto.START, range.getStart());
-        proto.write(PageRangeProto.END, range.getEnd());
+        long token = proto.start(idName, id);
+        proto.write("start", PageRangeProto.START, range.getStart());
+        proto.write("end", PageRangeProto.END, range.getEnd());
         proto.end(token);
     }
 
@@ -291,64 +306,70 @@
      *
      * @param context The context used to resolve resources
      * @param proto The proto to write to
+     * @param idName Clear text name of the proto-id
      * @param id The proto-id of the component name
      * @param printJobInfo The print job info to write
      */
-    public static void writePrintJobInfo(@NonNull Context context, @NonNull ProtoOutputStream proto,
-            long id, @NonNull PrintJobInfo printJobInfo) {
-        long token = proto.start(id);
-        proto.write(PrintJobInfoProto.LABEL, printJobInfo.getLabel());
+    public static void writePrintJobInfo(@NonNull Context context,
+            @NonNull DualDumpOutputStream proto, String idName, long id,
+            @NonNull PrintJobInfo printJobInfo) {
+        long token = proto.start(idName, id);
+        proto.write("label", PrintJobInfoProto.LABEL, printJobInfo.getLabel());
 
         PrintJobId printJobId = printJobInfo.getId();
         if (printJobId != null) {
-            proto.write(PrintJobInfoProto.PRINT_JOB_ID, printJobId.flattenToString());
+            proto.write("print_job_id", PrintJobInfoProto.PRINT_JOB_ID,
+                    printJobId.flattenToString());
         }
 
         int state = printJobInfo.getState();
         if (state >= PrintJobInfoProto.STATE_CREATED && state <= PrintJobInfoProto.STATE_CANCELED) {
-            proto.write(PrintJobInfoProto.STATE, state);
+            proto.write("state", PrintJobInfoProto.STATE, state);
         } else {
-            proto.write(PrintJobInfoProto.STATE, PrintJobInfoProto.STATE_UNKNOWN);
+            proto.write("state", PrintJobInfoProto.STATE, PrintJobInfoProto.STATE_UNKNOWN);
         }
 
         PrinterId printer = printJobInfo.getPrinterId();
         if (printer != null) {
-            writePrinterId(proto, PrintJobInfoProto.PRINTER, printer);
+            writePrinterId(proto, "printer", PrintJobInfoProto.PRINTER, printer);
         }
 
         String tag = printJobInfo.getTag();
         if (tag != null) {
-            proto.write(PrintJobInfoProto.TAG, tag);
+            proto.write("tag", PrintJobInfoProto.TAG, tag);
         }
 
-        proto.write(PrintJobInfoProto.CREATION_TIME, printJobInfo.getCreationTime());
+        proto.write("creation_time", PrintJobInfoProto.CREATION_TIME,
+                printJobInfo.getCreationTime());
 
         PrintAttributes attributes = printJobInfo.getAttributes();
         if (attributes != null) {
-            writePrintAttributes(context, proto, PrintJobInfoProto.ATTRIBUTES, attributes);
+            writePrintAttributes(context, proto, "attributes", PrintJobInfoProto.ATTRIBUTES,
+                    attributes);
         }
 
         PrintDocumentInfo docInfo = printJobInfo.getDocumentInfo();
         if (docInfo != null) {
-            writePrintDocumentInfo(proto, PrintJobInfoProto.DOCUMENT_INFO, docInfo);
+            writePrintDocumentInfo(proto, "document_info", PrintJobInfoProto.DOCUMENT_INFO,
+                    docInfo);
         }
 
-        proto.write(PrintJobInfoProto.IS_CANCELING, printJobInfo.isCancelling());
+        proto.write("is_canceling", PrintJobInfoProto.IS_CANCELING, printJobInfo.isCancelling());
 
         PageRange[] pages = printJobInfo.getPages();
         if (pages != null) {
             for (int i = 0; i < pages.length; i++) {
-                writePageRange(proto, PrintJobInfoProto.PAGES, pages[i]);
+                writePageRange(proto, "pages", PrintJobInfoProto.PAGES, pages[i]);
             }
         }
 
-        proto.write(PrintJobInfoProto.HAS_ADVANCED_OPTIONS,
+        proto.write("has_advanced_options", PrintJobInfoProto.HAS_ADVANCED_OPTIONS,
                 printJobInfo.getAdvancedOptions() != null);
-        proto.write(PrintJobInfoProto.PROGRESS, printJobInfo.getProgress());
+        proto.write("progress", PrintJobInfoProto.PROGRESS, printJobInfo.getProgress());
 
         CharSequence status = printJobInfo.getStatus(context.getPackageManager());
         if (status != null) {
-            proto.write(PrintJobInfoProto.STATUS, status.toString());
+            proto.write("status", PrintJobInfoProto.STATUS, status.toString());
         }
 
         proto.end(token);
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 c668e3d..31d22e0 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -69,7 +69,7 @@
     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);
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/view/windowlayoutparams.proto b/core/proto/android/view/windowlayoutparams.proto
index b81cd1f..f079e1e 100644
--- a/core/proto/android/view/windowlayoutparams.proto
+++ b/core/proto/android/view/windowlayoutparams.proto
@@ -58,7 +58,6 @@
   optional NeedsMenuState needs_menu_key = 22;
   optional .android.view.DisplayProto.ColorMode color_mode = 23;
   optional uint32 flags = 24;
-  optional uint64 flags_extra = 25;
   optional uint32 private_flags = 26;
   optional uint32 system_ui_visibility_flags = 27;
   optional uint32 subtree_system_ui_visibility_flags = 28;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 35dc624..d2a22d0 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" />
 
@@ -3398,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"
@@ -3684,6 +3692,12 @@
     <permission android:name="android.permission.MODIFY_QUIET_MODE"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an application to control remote animations. See
+         {@link ActivityOptions#makeRemoteAnimation}
+         @hide <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"
+        android:protectionLevel="signature|privileged" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
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/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 353a1a5..445b19b 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -39,6 +39,10 @@
             android:layout_height="@dimen/notification_progress_bar_height"
             android:layout_marginTop="@dimen/notification_progress_margin_top"
             layout="@layout/notification_template_progress" />
+        <include layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/notification_content_margin_bottom" />
     </LinearLayout>
     <include layout="@layout/notification_template_right_icon" />
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index 6b1049a..d47bff6 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -56,6 +56,12 @@
             android:id="@+id/notification_material_reply_container"
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />
+        <include layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginBottom="@dimen/notification_content_margin_bottom" />
     </LinearLayout>
     <include layout="@layout/notification_material_action_list" />
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_picture.xml b/core/res/res/layout/notification_template_material_big_picture.xml
index e94e646..76c0a67 100644
--- a/core/res/res/layout/notification_template_material_big_picture.xml
+++ b/core/res/res/layout/notification_template_material_big_picture.xml
@@ -64,6 +64,12 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 />
+        <include layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginBottom="@dimen/notification_content_margin_bottom" />
     </LinearLayout>
     <include layout="@layout/notification_material_action_list" />
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index 3c87f92..ac4c052 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -67,6 +67,12 @@
                 android:id="@+id/notification_material_reply_container"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content" />
+        <include layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginBottom="@dimen/notification_content_margin_bottom" />
     </LinearLayout>
     <include layout="@layout/notification_material_action_list" />
     <include layout="@layout/notification_template_right_icon" />
diff --git a/core/res/res/layout/notification_template_material_inbox.xml b/core/res/res/layout/notification_template_material_inbox.xml
index e4c91a4..718cf16 100644
--- a/core/res/res/layout/notification_template_material_inbox.xml
+++ b/core/res/res/layout/notification_template_material_inbox.xml
@@ -119,6 +119,12 @@
                 android:id="@+id/notification_material_reply_container"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content" />
+        <include layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginBottom="@dimen/notification_content_margin_bottom" />
     </LinearLayout>
     <include layout="@layout/notification_material_action_list" />
     <include layout="@layout/notification_template_right_icon" />
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index a72ad53..34f5ae8 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -47,6 +47,10 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:spacing="@dimen/notification_messaging_spacing" />
+            <include layout="@layout/notification_template_smart_reply_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/notification_content_margin_bottom" />
         </LinearLayout>
     </LinearLayout>
     <include layout="@layout/notification_material_action_list" />
diff --git a/core/res/res/layout/notification_template_smart_reply_container.xml b/core/res/res/layout/notification_template_smart_reply_container.xml
new file mode 100644
index 0000000..637241e
--- /dev/null
+++ b/core/res/res/layout/notification_template_smart_reply_container.xml
@@ -0,0 +1,24 @@
+<?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
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/smart_reply_container"
+              android:visibility="gone"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="horizontal">
+    <!-- SmartReplyView will be added here. -->
+</LinearLayout>
\ No newline at end of file
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/attrs.xml b/core/res/res/values/attrs.xml
index 79c8e04..addbbf5 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7760,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>
 
@@ -7984,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 f1e9662..3b02a96 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>
@@ -2783,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). -->
@@ -3243,4 +3249,7 @@
     <string name="config_fontFamilyButton">@string/font_family_button_material</string>
 
     <string translatable="false" name="config_batterySaverDeviceSpecificConfig"></string>
+
+    <!-- Package name that should be granted Notification Assistant access -->
+    <string name="config_defaultAssistantAccessPackage" translatable="false">android.ext.services</string>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f671b6b..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] -->
@@ -4800,13 +4803,10 @@
     <!--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 0dad20b..638f1b2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1722,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" />
@@ -2549,6 +2550,7 @@
   <java-symbol type="bool" name="config_mainBuiltInDisplayIsRound" />
 
   <java-symbol type="id" name="actions_container" />
+  <java-symbol type="id" name="smart_reply_container" />
   <java-symbol type="id" name="remote_input_tag" />
 
   <java-symbol type="attr" name="seekBarDialogPreferenceStyle" />
@@ -2889,8 +2891,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 +3204,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" />
@@ -3212,4 +3216,6 @@
   <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" />
+
+  <java-symbol type="string" name="config_defaultAssistantAccessPackage" />
 </resources>
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/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 4710595..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,
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/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 4732bec..c0958cd 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -75,6 +75,7 @@
 
     <privapp-permissions package="com.android.launcher3">
         <permission name="android.permission.BIND_APPWIDGET"/>
+        <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
         <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
     </privapp-permissions>
 
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/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/media/java/android/media/update/ApiLoader.java b/media/java/android/media/update/ApiLoader.java
index b57e02d..07483f6 100644
--- a/media/java/android/media/update/ApiLoader.java
+++ b/media/java/android/media/update/ApiLoader.java
@@ -49,8 +49,8 @@
                 Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
         sMediaLibrary = libContext.getClassLoader()
                 .loadClass(UPDATE_CLASS)
-                .getMethod(UPDATE_METHOD, Context.class)
-                .invoke(null, appContext);
+                .getMethod(UPDATE_METHOD, Context.class, Context.class)
+                .invoke(null, appContext, libContext);
         return sMediaLibrary;
     }
 }
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index 19f01c2..a1e2404 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SystemApi;
 import android.widget.MediaController2;
+import android.widget.VideoView2;
 
 /**
  * Interface for connecting the public API to an updatable implementation.
@@ -31,4 +32,5 @@
 public interface StaticProvider {
     MediaController2Provider createMediaController2(
             MediaController2 instance, ViewProvider superProvider);
+    VideoView2Provider createVideoView2(VideoView2 instance, ViewProvider superProvider);
 }
diff --git a/media/java/android/media/update/VideoView2Provider.java b/media/java/android/media/update/VideoView2Provider.java
new file mode 100644
index 0000000..6fc9bdc
--- /dev/null
+++ b/media/java/android/media/update/VideoView2Provider.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 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.media.update;
+
+import android.media.AudioAttributes;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.widget.MediaController2;
+import android.widget.VideoView2;
+
+import java.util.Map;
+
+/**
+ * Interface for connecting the public API to an updatable implementation.
+ *
+ * Each instance object is connected to one corresponding updatable object which implements the
+ * runtime behavior of that class. There should a corresponding provider method for all public
+ * methods.
+ *
+ * All methods behave as per their namesake in the public API.
+ *
+ * @see android.widget.VideoView2
+ *
+ * @hide
+ */
+// TODO @SystemApi
+public interface VideoView2Provider extends ViewProvider {
+    void start_impl();
+    void pause_impl();
+    int getDuration_impl();
+    int getCurrentPosition_impl();
+    void seekTo_impl(int msec);
+    boolean isPlaying_impl();
+    int getBufferPercentage_impl();
+    int getAudioSessionId_impl();
+    void showSubtitle_impl();
+    void hideSubtitle_impl();
+    void setAudioFocusRequest_impl(int focusGain);
+    void setAudioAttributes_impl(AudioAttributes attributes);
+    void setVideoPath_impl(String path);
+    void setVideoURI_impl(Uri uri);
+    void setVideoURI_impl(Uri uri, Map<String, String> headers);
+    void setMediaController2_impl(MediaController2 controllerView);
+    void setViewType_impl(int viewType);
+    int getViewType_impl();
+    void stopPlayback_impl();
+    void setOnPreparedListener_impl(MediaPlayer.OnPreparedListener l);
+    void setOnCompletionListener_impl(MediaPlayer.OnCompletionListener l);
+    void setOnErrorListener_impl(MediaPlayer.OnErrorListener l);
+    void setOnInfoListener_impl(MediaPlayer.OnInfoListener l);
+    void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l);
+}
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/model/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
index e0a3f6c..074f9ef 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
@@ -59,7 +59,9 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.HandlerCaller;
+import com.android.internal.print.DualDumpOutputStream;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.printspooler.R;
 import com.android.printspooler.util.ApprovedPrintServices;
@@ -159,43 +161,10 @@
         return new PrintSpooler();
     }
 
-    private void dumpLocked(PrintWriter pw, String[] args) {
-        String prefix = (args.length > 0) ? args[0] : "";
-        String tab = "  ";
-
-        pw.append(prefix).append("print jobs:").println();
-        final int printJobCount = mPrintJobs.size();
-        for (int i = 0; i < printJobCount; i++) {
-            PrintJobInfo printJob = mPrintJobs.get(i);
-            pw.append(prefix).append(tab).append(printJob.toString());
-            pw.println();
-        }
-
-        pw.append(prefix).append("print job files:").println();
-        File[] files = getFilesDir().listFiles();
-        if (files != null) {
-            final int fileCount = files.length;
-            for (int i = 0; i < fileCount; i++) {
-                File file = files[i];
-                if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
-                    pw.append(prefix).append(tab).append(file.getName()).println();
-                }
-            }
-        }
-
-        pw.append(prefix).append("approved print services:").println();
-        Set<String> approvedPrintServices = (new ApprovedPrintServices(this)).getApprovedServices();
-        if (approvedPrintServices != null) {
-            for (String approvedService : approvedPrintServices) {
-                pw.append(prefix).append(tab).append(approvedService).println();
-            }
-        }
-    }
-
-    private void dumpLocked(@NonNull ProtoOutputStream proto) {
+    private void dumpLocked(@NonNull DualDumpOutputStream proto) {
         int numPrintJobs = mPrintJobs.size();
         for (int i = 0; i < numPrintJobs; i++) {
-            writePrintJobInfo(this, proto, PrintSpoolerInternalStateProto.PRINT_JOBS,
+            writePrintJobInfo(this, proto, "print_jobs", PrintSpoolerInternalStateProto.PRINT_JOBS,
                     mPrintJobs.get(i));
         }
 
@@ -204,7 +173,8 @@
             for (int i = 0; i < files.length; i++) {
                 File file = files[i];
                 if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
-                    proto.write(PrintSpoolerInternalStateProto.PRINT_JOB_FILES, file.getName());
+                    proto.write("print_job_files", PrintSpoolerInternalStateProto.PRINT_JOB_FILES,
+                            file.getName());
                 }
             }
         }
@@ -214,8 +184,8 @@
             for (String approvedService : approvedPrintServices) {
                 ComponentName componentName = ComponentName.unflattenFromString(approvedService);
                 if (componentName != null) {
-                    writeComponentName(proto, PrintSpoolerInternalStateProto.APPROVED_SERVICES,
-                            componentName);
+                    writeComponentName(proto, "approved_services",
+                            PrintSpoolerInternalStateProto.APPROVED_SERVICES, componentName);
                 }
             }
         }
@@ -244,9 +214,15 @@
         try {
             synchronized (mLock) {
                 if (dumpAsProto) {
-                    dumpLocked(new ProtoOutputStream(fd));
+                    dumpLocked(new DualDumpOutputStream(new ProtoOutputStream(fd), null));
                 } else {
-                    dumpLocked(pw, args);
+                    try (FileOutputStream out = new FileOutputStream(fd)) {
+                        try (PrintWriter w = new PrintWriter(out)) {
+                            dumpLocked(new DualDumpOutputStream(null, new IndentingPrintWriter(w,
+                                    "  ")));
+                        }
+                    } catch (IOException ignored) {
+                    }
                 }
             }
         } finally {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
index adb4832..ae24c07 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
@@ -16,9 +16,9 @@
 package com.android.settingslib.core.lifecycle;
 
 import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-
 import static com.google.common.truth.Truth.assertThat;
 
+import android.arch.lifecycle.LifecycleOwner;
 import android.content.Context;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -48,6 +48,7 @@
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class LifecycleTest {
 
+    private LifecycleOwner mLifecycleOwner;
     private Lifecycle mLifecycle;
 
     public static class TestDialogFragment extends ObservableDialogFragment {
@@ -146,7 +147,8 @@
 
     @Before
     public void setUp() {
-        mLifecycle = new Lifecycle(() -> mLifecycle);
+        mLifecycleOwner = () -> mLifecycle;
+        mLifecycle = new Lifecycle(mLifecycleOwner);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java
index 5d5733e4..050877d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java
@@ -17,11 +17,11 @@
 package com.android.settingslib.development;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
+import android.arch.lifecycle.LifecycleOwner;
 import android.os.SystemProperties;
 import android.support.v7.preference.ListPreference;
 import android.support.v7.preference.Preference;
@@ -44,6 +44,7 @@
         shadows = SystemPropertiesTestImpl.class)
 public class LogpersistPreferenceControllerTest {
 
+    private LifecycleOwner mLifecycleOwner;
     private Lifecycle mLifecycle;
 
     @Mock
@@ -57,7 +58,8 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         SystemProperties.set("ro.debuggable", "1");
-        mLifecycle = new Lifecycle(() -> mLifecycle);
+        mLifecycleOwner = () -> mLifecycle;
+        mLifecycle = new Lifecycle(mLifecycleOwner);
         mController = new AbstractLogpersistPreferenceController(RuntimeEnvironment.application,
                 mLifecycle) {
             @Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
index 75b6c5f..88c57b5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
@@ -17,13 +17,13 @@
 package com.android.settingslib.widget;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.arch.lifecycle.LifecycleOwner;
 import android.support.v14.preference.PreferenceFragment;
 import android.support.v7.preference.PreferenceManager;
 import android.support.v7.preference.PreferenceScreen;
@@ -49,13 +49,15 @@
     @Mock
     private PreferenceScreen mScreen;
 
+    private LifecycleOwner mLifecycleOwner;
     private Lifecycle mLifecycle;
     private FooterPreferenceMixin mMixin;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mLifecycle = new Lifecycle(() -> mLifecycle);
+        mLifecycleOwner = () -> mLifecycle;
+        mLifecycle = new Lifecycle(mLifecycleOwner);
         when(mFragment.getPreferenceManager()).thenReturn(mock(PreferenceManager.class));
         when(mFragment.getPreferenceManager().getContext())
                 .thenReturn(ShadowApplication.getInstance().getApplicationContext());
diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml
index 287a888..fd4d296 100644
--- a/packages/SettingsProvider/AndroidManifest.xml
+++ b/packages/SettingsProvider/AndroidManifest.xml
@@ -8,6 +8,7 @@
                  android:process="system"
                  android:backupAgent="SettingsBackupAgent"
                  android:killAfterRestore="false"
+                 android:restoreAnyVersion="true"
                  android:icon="@mipmap/ic_launcher_settings"
                  android:defaultToDeviceProtectedStorage="true"
                  android:directBootAware="true">
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 1be0645..48a3a30 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -28,7 +28,7 @@
     <string name="def_bluetooth_disabled_profiles" translatable="false">0</string>
     <bool name="def_auto_time">true</bool>
     <bool name="def_auto_time_zone">true</bool>
-    <bool name="def_accelerometer_rotation">true</bool>
+    <bool name="def_accelerometer_rotation">false</bool>
     <!-- Default screen brightness, from 0 to 255.  102 is 40%. -->
     <integer name="def_screen_brightness">102</integer>
     <bool name="def_screen_brightness_automatic_mode">false</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index ae88227..c7ba4d6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -31,10 +31,12 @@
 import android.net.Uri;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.BackupUtils;
 import android.util.Log;
 
@@ -50,6 +52,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -147,6 +150,13 @@
     // stored in the full-backup tarfile as well, so should not be changed.
     private static final String STAGE_FILE = "flattened-data";
 
+    // List of keys that support restore to lower version of the SDK, introduced in Android P
+    private static final ArraySet<String> RESTORE_FROM_HIGHER_SDK_INT_SUPPORTED_KEYS =
+            new ArraySet<String>(Arrays.asList(new String[] {
+                KEY_NETWORK_POLICIES,
+                KEY_WIFI_NEW_CONFIG,
+            }));
+
     private SettingsHelper mSettingsHelper;
 
     private WifiManager mWifiManager;
@@ -209,6 +219,10 @@
     public void onRestore(BackupDataInput data, int appVersionCode,
             ParcelFileDescriptor newState) throws IOException {
 
+        if (DEBUG) {
+            Log.d(TAG, "onRestore(): appVersionCode: " + appVersionCode
+                    + "; Build.VERSION.SDK_INT: " + Build.VERSION.SDK_INT);
+        }
         // versionCode of com.android.providers.settings corresponds to SDK_INT
         mRestoredFromSdkInt = appVersionCode;
 
@@ -221,6 +235,15 @@
         while (data.readNextHeader()) {
             final String key = data.getKey();
             final int size = data.getDataSize();
+
+            // bail out of restoring from higher SDK_INT version for unsupported keys
+            if (appVersionCode > Build.VERSION.SDK_INT
+                    && !RESTORE_FROM_HIGHER_SDK_INT_SUPPORTED_KEYS.contains(key)) {
+                Log.w(TAG, "Not restoring unrecognized key '"
+                        + key + "' from future version " + appVersionCode);
+                continue;
+            }
+
             switch (key) {
                 case KEY_SYSTEM :
                     restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1167d69..175cff6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1584,6 +1584,11 @@
                 restriction = UserManager.DISALLOW_SAFE_BOOT;
                 break;
 
+            case Settings.Global.AIRPLANE_MODE_ON:
+                if ("0".equals(value)) return false;
+                restriction = UserManager.DISALLOW_AIRPLANE_MODE;
+                break;
+
             default:
                 if (setting != null && setting.startsWith(Settings.Global.DATA_ROAMING)) {
                     if ("0".equals(value)) return false;
@@ -2940,7 +2945,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 150;
+            private static final int SETTINGS_VERSION = 151;
 
             private final int mUserId;
 
@@ -3533,6 +3538,18 @@
                     currentVersion = 150;
                 }
 
+                if (currentVersion == 150) {
+                    // Version 151: Reset rotate locked setting for upgrading users
+                    final SettingsState systemSettings = getSystemSettingsLocked(userId);
+                    systemSettings.insertSettingLocked(
+                            Settings.System.ACCELEROMETER_ROTATION,
+                            getContext().getResources().getBoolean(
+                                    R.bool.def_accelerometer_rotation) ? "1" : "0",
+                            null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+
+                    currentVersion = 151;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
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/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index c52c0aa..61f7fe8 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -108,6 +108,7 @@
         public Supplier<Icon> iconSupplier;
         public int state = Tile.STATE_ACTIVE;
         public CharSequence label;
+        public CharSequence secondaryLabel;
         public CharSequence contentDescription;
         public CharSequence dualLabelContentDescription;
         public boolean disabledByPolicy;
@@ -122,6 +123,7 @@
             final boolean changed = !Objects.equals(other.icon, icon)
                     || !Objects.equals(other.iconSupplier, iconSupplier)
                     || !Objects.equals(other.label, label)
+                    || !Objects.equals(other.secondaryLabel, secondaryLabel)
                     || !Objects.equals(other.contentDescription, contentDescription)
                     || !Objects.equals(other.dualLabelContentDescription,
                             dualLabelContentDescription)
@@ -135,6 +137,7 @@
             other.icon = icon;
             other.iconSupplier = iconSupplier;
             other.label = label;
+            other.secondaryLabel = secondaryLabel;
             other.contentDescription = contentDescription;
             other.dualLabelContentDescription = dualLabelContentDescription;
             other.expandedAccessibilityClassName = expandedAccessibilityClassName;
@@ -156,6 +159,7 @@
             sb.append(",icon=").append(icon);
             sb.append(",iconSupplier=").append(iconSupplier);
             sb.append(",label=").append(label);
+            sb.append(",secondaryLabel=").append(secondaryLabel);
             sb.append(",contentDescription=").append(contentDescription);
             sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
             sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
index 56a3ee3..e25930c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
@@ -50,5 +50,7 @@
         }
 
         void setDarkIntensity(float intensity);
+
+        void setDelayTouchFeedback(boolean shouldDelay);
     }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
index 674ed5a..6131acc 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
@@ -14,6 +14,7 @@
 
 package com.android.systemui.plugins.statusbar.phone;
 
+import android.graphics.Canvas;
 import android.view.MotionEvent;
 
 import com.android.systemui.plugins.Plugin;
@@ -35,6 +36,12 @@
 
         public void setBarState(boolean vertical, boolean isRtl);
 
+        public void onDraw(Canvas canvas);
+
+        public void onDarkIntensityChange(float intensity);
+
+        public void onLayout(boolean changed, int left, int top, int right, int bottom);
+
         public default void destroy() { }
     }
 
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index c97cfc4..9adb550 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">
@@ -51,16 +50,6 @@
                 android:format12Hour="@string/keyguard_widget_12_hours_format"
                 android:format24Hour="@string/keyguard_widget_24_hours_format"
                 android:layout_marginBottom="@dimen/bottom_text_spacing_digital" />
-            <com.android.systemui.ChargingView
-                android:id="@+id/battery_doze"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_alignTop="@id/clock_view"
-                android:layout_alignBottom="@id/clock_view"
-                android:layout_toEndOf="@id/clock_view"
-                android:visibility="invisible"
-                android:src="@drawable/ic_aod_charging_24dp"
-                android:contentDescription="@string/accessibility_ambient_display_charging" />
             <View
                 android:id="@+id/clock_separator"
                 android:layout_width="16dp"
diff --git a/packages/SystemUI/res/drawable/ic_face_unlock.xml b/packages/SystemUI/res/drawable/ic_face_unlock.xml
new file mode 100644
index 0000000..29c2275
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_face_unlock.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ 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="48dp"
+        android:height="48dp"
+        android:viewportHeight="24.0"
+        android:viewportWidth="24.0">
+    <path android:fillColor="?attr/wallpaperTextColor"
+          android:strokeColor="?attr/wallpaperTextColor"
+          android:strokeWidth="1"
+          android:pathData="M9,11.75C8.31,11.75 7.75,12.31 7.75,13C7.75,13.69 8.31,14.25 9,14.25C9.69,14.25 10.25,13.69 10.25,13C10.25,12.31 9.69,11.75 9,11.75ZM15,11.75C14.31,11.75 13.75,12.31 13.75,13C13.75,13.69 14.31,14.25 15,14.25C15.69,14.25 16.25,13.69 16.25,13C16.25,12.31 15.69,11.75 15,11.75ZM12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2ZM12,20C7.59,20 4,16.41 4,12C4,11.71 4.02,11.42 4.05,11.14C6.41,10.09 8.28,8.16 9.26,5.77C11.07,8.33 14.05,10 17.42,10C18.2,10 18.95,9.91 19.67,9.74C19.88,10.45 20,11.21 20,12C20,16.41 16.41,20 12,20Z"
+    />
+</vector>
diff --git a/packages/SystemUI/res/drawable/qs_background_primary.xml b/packages/SystemUI/res/drawable/qs_background_primary.xml
index 03bba53..dd74cadd 100644
--- a/packages/SystemUI/res/drawable/qs_background_primary.xml
+++ b/packages/SystemUI/res/drawable/qs_background_primary.xml
@@ -16,5 +16,6 @@
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape>
         <solid android:color="@color/qs_background_dark"/>
+        <corners android:radius="?android:attr/dialogCornerRadius" />
     </shape>
 </inset>
diff --git a/packages/SystemUI/res/drawable/smart_reply_button_background.xml b/packages/SystemUI/res/drawable/smart_reply_button_background.xml
new file mode 100644
index 0000000..1cd1451
--- /dev/null
+++ b/packages/SystemUI/res/drawable/smart_reply_button_background.xml
@@ -0,0 +1,27 @@
+<?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
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/notification_ripple_untinted_color">
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/smart_reply_button_corner_radius"/>
+            <solid android:color="@color/smart_reply_button_background"/>
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 2fd4df4..d0d379c 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -40,6 +40,7 @@
             android:gravity="center_horizontal"
             android:textStyle="italic"
             android:textColor="?attr/wallpaperTextColorSecondary"
+            android:textSize="16sp"
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:visibility="gone" />
 
@@ -50,6 +51,7 @@
             android:gravity="center_horizontal"
             android:textStyle="italic"
             android:textColor="?attr/wallpaperTextColorSecondary"
+            android:textSize="16sp"
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:accessibilityLiveRegion="polite" />
 
diff --git a/packages/SystemUI/res/layout/smart_reply_button.xml b/packages/SystemUI/res/layout/smart_reply_button.xml
new file mode 100644
index 0000000..4ac41d5
--- /dev/null
+++ b/packages/SystemUI/res/layout/smart_reply_button.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
+  -->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@android:style/Widget.Material.Button.Borderless.Small"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/smart_reply_button_spacing"
+        android:paddingVertical="@dimen/smart_reply_button_padding_vertical"
+        android:paddingHorizontal="@dimen/smart_reply_button_corner_radius"
+        android:background="@drawable/smart_reply_button_background"
+        android:gravity="center"
+        android:fontFamily="sans-serif"
+        android:textSize="@dimen/smart_reply_button_font_size"
+        android:textColor="@color/smart_reply_button_text"
+        android:textStyle="normal"
+        android:singleLine="true"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/smart_reply_view.xml b/packages/SystemUI/res/layout/smart_reply_view.xml
new file mode 100644
index 0000000..6d53386
--- /dev/null
+++ b/packages/SystemUI/res/layout/smart_reply_view.xml
@@ -0,0 +1,27 @@
+<?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
+  -->
+
+<!-- LinearLayout -->
+<com.android.systemui.statusbar.policy.SmartReplyView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/smart_reply_view"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:layout_gravity="end">
+    <!-- smart_reply_button(s) will be added here. -->
+</com.android.systemui.statusbar.policy.SmartReplyView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index bef0830..c5e5ee1 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -43,6 +43,8 @@
             android:layout_width="@dimen/qs_panel_width"
             android:layout_height="match_parent"
             android:layout_gravity="@integer/notification_panel_layout_gravity"
+            android:layout_marginStart="@dimen/notification_side_paddings"
+            android:layout_marginEnd="@dimen/notification_side_paddings"
             android:clipToPadding="false"
             android:clipChildren="false"
             systemui:viewType="com.android.systemui.plugins.qs.QS" />
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index 7f37087..4614999 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -54,6 +54,18 @@
         android:paddingStart="8dp"
         />
 
+    <ImageButton
+        android:id="@+id/helper"
+        android:layout_width="48dp"
+        android:layout_height="@*android:dimen/notification_header_height"
+        android:layout_gravity="top|end"
+        android:layout_marginEnd="6dp"
+        android:src="@drawable/ic_dnd"
+        android:tint="#FF0000"
+        android:background="@drawable/ripple_drawable"
+        android:visibility="visible"
+        />
+
     <ViewStub
         android:layout="@layout/notification_children_container"
         android:id="@+id/child_container_stub"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index f244d88..0f4c3b8 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -143,6 +143,9 @@
 
     <color name="remote_input_accent">#eeeeee</color>
 
+    <color name="quick_step_track_background_dark">#61000000</color>
+    <color name="quick_step_track_background_light">#4DFFFFFF</color>
+
     <!-- Keyboard shortcuts colors -->
     <color name="ksh_application_group_color">#fff44336</color>
     <color name="ksh_keyword_color">#d9000000</color>
@@ -153,4 +156,6 @@
 
     <color name="zen_introduction">#ffffffff</color>
 
+    <color name="smart_reply_button_text">#ff4285f4</color><!-- blue 500 -->
+    <color name="smart_reply_button_background">#fff7f7f7</color>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 178c1c0..7a670fd 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -579,6 +579,7 @@
     <dimen name="keyguard_affordance_icon_width">24dp</dimen>
 
     <dimen name="keyguard_indication_margin_bottom">65dp</dimen>
+    <dimen name="keyguard_indication_margin_bottom_ambient">30dp</dimen>
 
     <!-- The text size for battery level -->
     <dimen name="battery_level_text_size">12sp</dimen>
@@ -870,6 +871,8 @@
     <dimen name="rounded_corner_radius">0dp</dimen>
     <dimen name="rounded_corner_content_padding">0dp</dimen>
     <dimen name="nav_content_padding">0dp</dimen>
+    <dimen name="nav_quick_scrub_track_edge_padding">32dp</dimen>
+    <dimen name="nav_quick_scrub_track_thickness">2dp</dimen>
 
     <!-- Intended corner radius when drawing the mobile signal -->
     <dimen name="stat_sys_mobile_signal_corner_radius">0.75dp</dimen>
@@ -879,4 +882,9 @@
     <!-- Home button padding for sizing -->
     <dimen name="home_padding">15dp</dimen>
 
+    <!-- Smart reply button -->
+    <dimen name="smart_reply_button_corner_radius">24dip</dimen>
+    <dimen name="smart_reply_button_spacing">8dp</dimen>
+    <dimen name="smart_reply_button_padding_vertical">4dp</dimen>
+    <dimen name="smart_reply_button_font_size">14sp</dimen>
 </resources>
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/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 173a90a..64fa9c6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -22,4 +22,8 @@
 oneway interface IOverviewProxy {
     void onBind(in ISystemUiProxy sysUiProxy);
     void onMotionEvent(in MotionEvent event);
+    void onQuickSwitch();
+    void onQuickScrubStart();
+    void onQuickScrubEnd();
+    void onQuickScrubProgress(float progress);
 }
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/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 8135c61..d80a336 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -151,6 +151,7 @@
 
         mClickActions.clear();
         final int subItemsCount = subItems.size();
+        final int blendedColor = getTextColor();
 
         for (int i = 0; i < subItemsCount; i++) {
             SliceItem item = subItems.get(i);
@@ -159,7 +160,7 @@
             KeyguardSliceButton button = mRow.findViewWithTag(itemTag);
             if (button == null) {
                 button = new KeyguardSliceButton(mContext);
-                button.setTextColor(mTextColor);
+                button.setTextColor(blendedColor);
                 button.setTag(itemTag);
             } else {
                 mRow.removeView(button);
@@ -258,7 +259,7 @@
     }
 
     private void updateTextColors() {
-        final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+        final int blendedColor = getTextColor();
         mTitle.setTextColor(blendedColor);
         int childCount = mRow.getChildCount();
         for (int i = 0; i < childCount; i++) {
@@ -322,6 +323,10 @@
         }
     }
 
+    public int getTextColor() {
+        return ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+    }
+
     /**
      * Representation of an item that appears under the clock on main keyguard message.
      * Shows optional separator.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 4b9a874..2873afb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -16,7 +16,6 @@
 
 package com.android.keyguard;
 
-import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -40,7 +39,6 @@
 import android.widget.TextView;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.ChargingView;
 
 import com.google.android.collect.Sets;
 
@@ -60,7 +58,6 @@
     private View mClockSeparator;
     private TextView mOwnerInfo;
     private ViewGroup mClockContainer;
-    private ChargingView mBatteryDoze;
     private KeyguardSliceView mKeyguardSlice;
     private Runnable mPendingMarqueeStart;
     private Handler mHandler;
@@ -155,11 +152,9 @@
             mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext));
         }
         mOwnerInfo = findViewById(R.id.owner_info);
-        mBatteryDoze = findViewById(R.id.battery_doze);
         mKeyguardSlice = findViewById(R.id.keyguard_status_area);
         mClockSeparator = findViewById(R.id.clock_separator);
-        mVisibleInDoze = Sets.newArraySet(mBatteryDoze, mClockView, mKeyguardSlice,
-                mClockSeparator);
+        mVisibleInDoze = Sets.newArraySet(mClockView, mKeyguardSlice, mClockSeparator);
         mTextColor = mClockView.getCurrentTextColor();
 
         mKeyguardSlice.setListener(this::onSliceContentChanged);
@@ -184,10 +179,6 @@
         mClockView.setTranslationY(translation);
         mClockView.setScaleX(clockScale);
         mClockView.setScaleY(clockScale);
-        final float batteryTranslation =
-                -(mClockView.getWidth() - (mClockView.getWidth() * clockScale)) / 2;
-        mBatteryDoze.setTranslationX(batteryTranslation);
-        mBatteryDoze.setTranslationY(translation);
         mClockSeparator.setVisibility(hasHeader ? VISIBLE : GONE);
     }
 
@@ -310,7 +301,7 @@
         }
     }
 
-    public void setDark(float darkAmount) {
+    public void setDarkAmount(float darkAmount) {
         if (mDarkAmount == darkAmount) {
             return;
         }
@@ -331,7 +322,6 @@
 
         final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount);
         updateDozeVisibleViews();
-        mBatteryDoze.setDark(dark);
         mKeyguardSlice.setDark(darkAmount);
         mClockView.setTextColor(blendedTextColor);
         mClockSeparator.setBackgroundColor(blendedTextColor);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index ab2bce8..e58ad05 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1613,11 +1613,10 @@
         }
     }
 
-    private static boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) {
+    private boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) {
         final boolean nowPluggedIn = current.isPluggedIn();
         final boolean wasPluggedIn = old.isPluggedIn();
-        final boolean stateChangedWhilePluggedIn =
-            wasPluggedIn == true && nowPluggedIn == true
+        final boolean stateChangedWhilePluggedIn = wasPluggedIn && nowPluggedIn
             && (old.status != current.status);
 
         // change in plug state is always interesting
@@ -1630,6 +1629,11 @@
             return true;
         }
 
+        // change in battery level while keyguard visible
+        if (mKeyguardIsVisible && old.level != current.level) {
+            return true;
+        }
+
         // change where battery needs charging
         if (!nowPluggedIn && current.isBatteryLow() && current.level != old.level) {
             return true;
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/ChargingView.java b/packages/SystemUI/src/com/android/systemui/ChargingView.java
deleted file mode 100644
index 33f8b06..0000000
--- a/packages/SystemUI/src/com/android/systemui/ChargingView.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.os.UserHandle;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-
-import com.android.internal.hardware.AmbientDisplayConfiguration;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-
-/**
- * A view that only shows its drawable while the phone is charging.
- *
- * Also reloads its drawable upon density changes.
- */
-public class ChargingView extends ImageView implements
-        BatteryController.BatteryStateChangeCallback,
-        ConfigurationController.ConfigurationListener {
-
-    private static final long CHARGING_INDICATION_DELAY_MS = 1000;
-
-    private final AmbientDisplayConfiguration mConfig;
-    private final Runnable mClearSuppressCharging = this::clearSuppressCharging;
-    private BatteryController mBatteryController;
-    private int mImageResource;
-    private boolean mCharging;
-    private boolean mDark;
-    private boolean mSuppressCharging;
-
-
-    private void clearSuppressCharging() {
-        mSuppressCharging = false;
-        removeCallbacks(mClearSuppressCharging);
-        updateVisibility();
-    }
-
-    public ChargingView(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-
-        mConfig = new AmbientDisplayConfiguration(context);
-
-        TypedArray a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.src});
-        int srcResId = a.getResourceId(0, 0);
-
-        if (srcResId != 0) {
-            mImageResource = srcResId;
-        }
-
-        a.recycle();
-
-        updateVisibility();
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mBatteryController = Dependency.get(BatteryController.class);
-        mBatteryController.addCallback(this);
-        Dependency.get(ConfigurationController.class).addCallback(this);
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mBatteryController.removeCallback(this);
-        Dependency.get(ConfigurationController.class).removeCallback(this);
-        removeCallbacks(mClearSuppressCharging);
-    }
-
-    @Override
-    public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
-        boolean startCharging = charging && !mCharging;
-        if (startCharging && deviceWillWakeUpWhenPluggedIn() && mDark) {
-            // We're about to wake up, and thus don't want to show the indicator just for it to be
-            // hidden again.
-            clearSuppressCharging();
-            mSuppressCharging = true;
-            postDelayed(mClearSuppressCharging, CHARGING_INDICATION_DELAY_MS);
-        }
-        mCharging = charging;
-        updateVisibility();
-    }
-
-    private boolean deviceWillWakeUpWhenPluggedIn() {
-        boolean plugTurnsOnScreen = getResources().getBoolean(
-                com.android.internal.R.bool.config_unplugTurnsOnScreen);
-        boolean aod = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
-        return !aod && plugTurnsOnScreen;
-    }
-
-    @Override
-    public void onDensityOrFontScaleChanged() {
-        setImageResource(mImageResource);
-    }
-
-    public void setDark(boolean dark) {
-        mDark = dark;
-        if (!dark) {
-            clearSuppressCharging();
-        }
-        updateVisibility();
-    }
-
-    private void updateVisibility() {
-        setVisibility(mCharging && !mSuppressCharging && mDark ? VISIBLE : INVISIBLE);
-    }
-}
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..5d2e4d0 100644
--- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
+++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
@@ -16,20 +16,14 @@
 
 package com.android.systemui;
 
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
 import android.content.Context;
-import android.database.ContentObserver;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.PorterDuff;
-import android.graphics.Region;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
 import android.view.DisplayCutout;
 import android.view.Gravity;
 import android.view.View;
@@ -38,25 +32,35 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 
-import java.util.Collections;
-import java.util.List;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 
 /**
  * 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) {
@@ -88,23 +92,12 @@
                 PixelFormat.TRANSLUCENT);
         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
                 | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
-        lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
+        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         lp.setTitle("EmulatedDisplayCutout");
         lp.gravity = Gravity.TOP;
         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/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
index 6f7a270..c960fa1 100644
--- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
+++ b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
@@ -14,6 +14,8 @@
 
 package com.android.systemui;
 
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
 import static com.android.systemui.tuner.TunablePadding.FLAG_START;
 import static com.android.systemui.tuner.TunablePadding.FLAG_END;
 
@@ -163,7 +165,7 @@
                 | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
         lp.setTitle("RoundedOverlay");
         lp.gravity = Gravity.TOP;
-        lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
+        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         return lp;
     }
 
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/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index bfe07a9..0486a9d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -373,7 +373,7 @@
             if (menuState == MENU_STATE_FULL) {
                 mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
             } else {
-                mMenuContainerAnimator.playTogether(settingsAnim, dismissAnim);
+                mMenuContainerAnimator.playTogether(dismissAnim);
             }
             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
             mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
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/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 33b5268..7f0acc2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -17,13 +17,18 @@
 package com.android.systemui.qs;
 
 import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
 import android.graphics.Point;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 import android.widget.FrameLayout;
 
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.statusbar.ExpandableOutlineView;
 
 /**
  * Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader}
@@ -31,6 +36,7 @@
 public class QSContainerImpl extends FrameLayout {
 
     private final Point mSizePoint = new Point();
+    private final Path mClipPath = new Path();
 
     private int mHeightOverride = -1;
     protected View mQSPanel;
@@ -40,6 +46,7 @@
     private QSCustomizer mQSCustomizer;
     private View mQSFooter;
     private float mFullElevation;
+    private float mRadius;
 
     public QSContainerImpl(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -54,6 +61,8 @@
         mQSCustomizer = findViewById(R.id.qs_customize);
         mQSFooter = findViewById(R.id.qs_footer);
         mFullElevation = mQSPanel.getElevation();
+        mRadius = getResources().getDimensionPixelSize(
+                Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
 
         setClickable(true);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -93,6 +102,18 @@
         updateExpansion();
     }
 
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        boolean ret;
+        canvas.save();
+        if (child != mQSCustomizer) {
+            canvas.clipPath(mClipPath);
+        }
+        ret = super.drawChild(canvas, child, drawingTime);
+        canvas.restore();
+        return ret;
+    }
+
     /**
      * Overrides the height of this view (post-layout), so that the content is clipped to that
      * height and the background is set to that height.
@@ -110,6 +131,10 @@
         mQSDetail.setBottom(getTop() + height);
         // Pin QS Footer to the bottom of the panel.
         mQSFooter.setTranslationY(height - mQSFooter.getHeight());
+
+        ExpandableOutlineView.getRoundedRectPath(0, 0, getWidth(), height, mRadius,
+                mRadius,
+                mClipPath);
     }
 
     protected int calculateContainerHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index c249e37..0f83078 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -103,7 +103,7 @@
 
             if (iv instanceof SlashImageView) {
                 ((SlashImageView) iv).setAnimationEnabled(shouldAnimate);
-                ((SlashImageView) iv).setState(state.slash, d);
+                ((SlashImageView) iv).setState(null, d);
             } else {
                 iv.setImageDrawable(d);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 4d0e60d..acd327b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -13,7 +13,12 @@
  */
 package com.android.systemui.qs.tileimpl;
 
+import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
@@ -22,16 +27,21 @@
 import android.os.Message;
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
 import android.widget.Switch;
 
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.*;
+import com.android.systemui.plugins.qs.QSIconView;
+import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 
 public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
@@ -47,6 +57,12 @@
     private boolean mCollapsedView;
     private boolean mClicked;
 
+    private final ImageView mBg;
+    private final int mColorActive;
+    private final int mColorInactive;
+    private final int mColorDisabled;
+    private int mCircleColor;
+
     public QSTileBaseView(Context context, QSIconView icon) {
         this(context, icon, false);
     }
@@ -60,6 +76,10 @@
         mIconFrame.setForegroundGravity(Gravity.CENTER);
         int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
         addView(mIconFrame, new LayoutParams(size, size));
+        mBg = new ImageView(getContext());
+        mBg.setScaleType(ScaleType.FIT_CENTER);
+        mBg.setImageResource(R.drawable.ic_qs_circle);
+        mIconFrame.addView(mBg);
         mIcon = icon;
         FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -73,6 +93,11 @@
         setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
         setBackground(mTileBackground);
 
+        mColorActive = Utils.getColorAttr(context, android.R.attr.colorAccent);
+        mColorDisabled = Utils.getDisabled(context,
+                Utils.getColorAttr(context, android.R.attr.textColorTertiary));
+        mColorInactive = Utils.getColorAttr(context, android.R.attr.textColorSecondary);
+
         setPadding(0, 0, 0, 0);
         setClipChildren(false);
         setClipToPadding(false);
@@ -80,6 +105,10 @@
         setFocusable(true);
     }
 
+    public View getBgCicle() {
+        return mBg;
+    }
+
     protected Drawable newTileBackground() {
         final int[] attrs = new int[]{android.R.attr.selectableItemBackgroundBorderless};
         final TypedArray ta = getContext().obtainStyledAttributes(attrs);
@@ -150,6 +179,20 @@
     }
 
     protected void handleStateChanged(QSTile.State state) {
+        int circleColor = getCircleColor(state.state);
+        if (circleColor != mCircleColor) {
+            if (mBg.isShown()) {
+                ValueAnimator animator = ValueAnimator.ofArgb(mCircleColor, circleColor)
+                        .setDuration(QS_ANIM_LENGTH);
+                animator.addUpdateListener(animation -> mBg.setImageTintList(ColorStateList.valueOf(
+                        (Integer) animation.getAnimatedValue())));
+                animator.start();
+            } else {
+                QSIconViewImpl.setTint(mBg, circleColor);
+            }
+            mCircleColor = circleColor;
+        }
+
         setClickable(state.state != Tile.STATE_UNAVAILABLE);
         mIcon.setIcon(state);
         setContentDescription(state.contentDescription);
@@ -163,6 +206,19 @@
         }
     }
 
+    private int getCircleColor(int state) {
+        switch (state) {
+            case Tile.STATE_ACTIVE:
+                return mColorActive;
+            case Tile.STATE_INACTIVE:
+            case Tile.STATE_UNAVAILABLE:
+                return mColorDisabled;
+            default:
+                Log.e(TAG, "Invalid state " + state);
+                return 0;
+        }
+    }
+
     @Override
     public void setClickable(boolean clickable) {
         super.setClickable(clickable);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 576a447..72592829 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -373,11 +373,11 @@
         switch (state) {
             case Tile.STATE_UNAVAILABLE:
                 return Utils.getDisabled(context,
-                        Utils.getColorAttr(context, android.R.attr.colorForeground));
+                        Utils.getColorAttr(context, android.R.attr.textColorSecondary));
             case Tile.STATE_INACTIVE:
-                return Utils.getColorAttr(context, android.R.attr.textColorHint);
+                return Utils.getColorAttr(context, android.R.attr.textColorSecondary);
             case Tile.STATE_ACTIVE:
-                return Utils.getColorAttr(context, android.R.attr.textColorPrimary);
+                return Utils.getColorAttr(context, android.R.attr.colorPrimary);
             default:
                 Log.e("QSTile", "Invalid state " + state);
                 return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
index 263dac0..a226f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
@@ -18,6 +18,7 @@
 import android.content.res.Configuration;
 import android.service.quicksettings.Tile;
 import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
 import android.text.style.ForegroundColorSpan;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -28,6 +29,7 @@
 
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
+import com.android.systemui.R.id;
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
 
@@ -36,8 +38,10 @@
 /** View that represents a standard quick settings tile. **/
 public class QSTileView extends QSTileBaseView {
 
+    private static final boolean DUAL_TARGET_ALLOWED = false;
     private View mDivider;
     protected TextView mLabel;
+    private TextView mSecondLine;
     private ImageView mPadLock;
     private int mState;
     private ViewGroup mLabelContainer;
@@ -86,6 +90,8 @@
         mDivider = mLabelContainer.findViewById(R.id.underline);
         mExpandIndicator = mLabelContainer.findViewById(R.id.expand_indicator);
         mExpandSpace = mLabelContainer.findViewById(R.id.expand_space);
+        mSecondLine = mLabelContainer.findViewById(R.id.app_label);
+        mSecondLine.setAlpha(.6f);
 
         addView(mLabelContainer);
     }
@@ -103,14 +109,20 @@
             mState = state.state;
             mLabel.setText(state.label);
         }
-        mExpandIndicator.setVisibility(state.dualTarget ? View.VISIBLE : View.GONE);
-        mExpandSpace.setVisibility(state.dualTarget ? View.VISIBLE : View.GONE);
-        mLabelContainer.setContentDescription(state.dualTarget ? state.dualLabelContentDescription
+        if (!Objects.equal(mSecondLine.getText(), state.secondaryLabel)) {
+            mSecondLine.setText(state.secondaryLabel);
+            mSecondLine.setVisibility(TextUtils.isEmpty(state.secondaryLabel) ? View.GONE
+                    : View.VISIBLE);
+        }
+        boolean dualTarget = DUAL_TARGET_ALLOWED && state.dualTarget;
+        mExpandIndicator.setVisibility(dualTarget ? View.VISIBLE : View.GONE);
+        mExpandSpace.setVisibility(dualTarget ? View.VISIBLE : View.GONE);
+        mLabelContainer.setContentDescription(dualTarget ? state.dualLabelContentDescription
                 : null);
-        if (state.dualTarget != mLabelContainer.isClickable()) {
-            mLabelContainer.setClickable(state.dualTarget);
-            mLabelContainer.setLongClickable(state.dualTarget);
-            mLabelContainer.setBackground(state.dualTarget ? newTileBackground() : null);
+        if (dualTarget != mLabelContainer.isClickable()) {
+            mLabelContainer.setClickable(dualTarget);
+            mLabelContainer.setLongClickable(dualTarget);
+            mLabelContainer.setBackground(dualTarget ? newTileBackground() : null);
         }
         mLabel.setEnabled(!state.disabledByPolicy);
         mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 0e4a9fe..fff9f8e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -125,11 +125,11 @@
             state.slash = new SlashState();
         }
         state.slash.isSlashed = !enabled;
+        state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
         if (enabled) {
-            state.label = null;
             if (connected) {
                 state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_connected);
-                state.label = mController.getLastDeviceName();
+                state.secondaryLabel = mController.getLastDeviceName();
                 CachedBluetoothDevice lastDevice = mController.getLastDevice();
                 if (lastDevice != null) {
                     int batteryLevel = lastDevice.getBatteryLevel();
@@ -140,25 +140,20 @@
                     }
                 }
                 state.contentDescription = mContext.getString(
-                        R.string.accessibility_bluetooth_name, state.label);
+                        R.string.accessibility_bluetooth_name, state.secondaryLabel);
             } else if (state.isTransient) {
                 state.icon = ResourceIcon.get(R.drawable.ic_bluetooth_transient_animation);
                 state.contentDescription = mContext.getString(
                         R.string.accessibility_quick_settings_bluetooth_connecting);
-                state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
             } else {
                 state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
                 state.contentDescription = mContext.getString(
                         R.string.accessibility_quick_settings_bluetooth_on) + ","
                         + mContext.getString(R.string.accessibility_not_connected);
             }
-            if (TextUtils.isEmpty(state.label)) {
-                state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
-            }
             state.state = Tile.STATE_ACTIVE;
         } else {
             state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
-            state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
             state.contentDescription = mContext.getString(
                     R.string.accessibility_quick_settings_bluetooth_off);
             state.state = Tile.STATE_INACTIVE;
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/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index d1e6dcc..bf8a64c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -33,6 +33,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
+import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
@@ -173,6 +174,7 @@
     private FalsingManager mFalsingManager;
     private AboveShelfChangedListener mAboveShelfChangedListener;
     private HeadsUpManager mHeadsUpManager;
+    private View mHelperButton;
 
     private boolean mJustClicked;
     private boolean mIconAnimationRunning;
@@ -387,6 +389,9 @@
         updateLimits();
         updateIconVisibilities();
         updateShelfIconColor();
+
+        showBlockingHelper(mEntry.userSentiment ==
+                NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
     }
 
     @VisibleForTesting
@@ -1318,6 +1323,10 @@
         requestLayout();
     }
 
+    public void showBlockingHelper(boolean show) {
+        mHelperButton.setVisibility(show ? View.VISIBLE : View.GONE);
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -1325,6 +1334,12 @@
         mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
         mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout};
 
+        final NotificationGutsManager gutsMan = Dependency.get(NotificationGutsManager.class);
+        mHelperButton = findViewById(R.id.helper);
+        mHelperButton.setOnClickListener(view -> {
+            doLongClickCallback();
+        });
+
         for (NotificationContentView l : mLayouts) {
             l.setExpandClickListener(mExpandClickListener);
             l.setContainingNotification(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 85400a1..43047ed6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -52,6 +52,8 @@
 import com.android.systemui.util.wakelock.SettableWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
+import java.text.NumberFormat;
+
 /**
  * Controls the indications and error messages shown on the Keyguard
  */
@@ -87,6 +89,7 @@
     private boolean mPowerCharged;
     private int mChargingSpeed;
     private int mChargingWattage;
+    private int mBatteryLevel;
     private String mMessageToShowOnScreenOn;
 
     private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
@@ -285,14 +288,18 @@
             // Walk down a precedence-ordered list of what indication
             // should be shown based on user or device state
             if (mDozing) {
-                // If we're dozing, never show a persistent indication.
+                mTextView.setTextColor(Color.WHITE);
                 if (!TextUtils.isEmpty(mTransientIndication)) {
                     // When dozing we ignore any text color and use white instead, because
                     // colors can be hard to read in low brightness.
-                    mTextView.setTextColor(Color.WHITE);
                     mTextView.switchIndication(mTransientIndication);
+                } else if (mPowerPluggedIn) {
+                    String indication = computePowerIndication();
+                    mTextView.switchIndication(indication);
                 } else {
-                    mTextView.switchIndication(null);
+                    String percentage = NumberFormat.getPercentInstance()
+                            .format(mBatteryLevel / 100f);
+                    mTextView.switchIndication(percentage);
                 }
                 return;
             }
@@ -422,6 +429,7 @@
             mPowerCharged = status.isCharged();
             mChargingWattage = status.maxChargingWattage;
             mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold);
+            mBatteryLevel = status.level;
             updateIndication();
             if (mDozing) {
                 if (!wasPluggedIn && mPowerPluggedIn) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index d0417b5..7e0dba5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -86,6 +86,8 @@
         public RemoteViews cachedAmbientContentView;
         public CharSequence remoteInputText;
         public List<SnoozeCriterion> snoozeCriteria;
+        public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL;
+
         private int mCachedContrastColor = COLOR_INVALID;
         private int mCachedContrastColorIsFor = COLOR_INVALID;
         private InflationTask mRunningTask = null;
@@ -463,6 +465,7 @@
                     }
                     entry.channel = getChannel(entry.key);
                     entry.snoozeCriteria = getSnoozeCriteria(entry.key);
+                    entry.userSentiment = mTmpRanking.getUserSentiment();
                 }
             }
         }
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 78ee040..9b123cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -14,7 +14,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.graphics.drawable.Drawable;
 import android.view.View;
 
 import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
@@ -39,6 +38,7 @@
     private Integer mAlpha;
     private Float mDarkIntensity;
     private Integer mVisibility = -1;
+    private Boolean mDelayTouchFeedback;
     private KeyButtonDrawable mImageDrawable;
     private View mCurrentView;
     private boolean mVertical;
@@ -71,10 +71,10 @@
         if (mImageDrawable != null) {
             ((ButtonInterface) view).setImageDrawable(mImageDrawable);
         }
-
-        if (view instanceof  ButtonInterface) {
-            ((ButtonInterface) view).setVertical(mVertical);
+        if (mDelayTouchFeedback != null) {
+            ((ButtonInterface) view).setDelayTouchFeedback(mDelayTouchFeedback);
         }
+        ((ButtonInterface) view).setVertical(mVertical);
     }
 
     public int getId() {
@@ -134,6 +134,14 @@
         }
     }
 
+    public void setDelayTouchFeedback(boolean delay) {
+        mDelayTouchFeedback = delay;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            ((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay);
+        }
+    }
+
     public void setOnClickListener(View.OnClickListener clickListener) {
         mClickListener = clickListener;
         final int N = mViews.size();
@@ -166,6 +174,14 @@
         }
     }
 
+    public void setClickable(boolean clickable) {
+        abortCurrentGesture();
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setClickable(clickable);
+        }
+    }
+
     public ArrayList<View> getViews() {
         return mViews;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 01b3b44..ca66e98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -51,6 +51,7 @@
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
@@ -166,6 +167,10 @@
     private String mLeftButtonStr;
     private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
     private boolean mDozing;
+    private int mIndicationBottomMargin;
+    private int mIndicationBottomMarginAmbient;
+    private float mDarkAmount;
+    private int mBurnInXOffset;
 
     public KeyguardBottomAreaView(Context context) {
         this(context, null);
@@ -235,6 +240,10 @@
         mEnterpriseDisclosure = findViewById(
                 R.id.keyguard_indication_enterprise_disclosure);
         mIndicationText = findViewById(R.id.keyguard_indication_text);
+        mIndicationBottomMargin = getResources().getDimensionPixelSize(
+                R.dimen.keyguard_indication_margin_bottom);
+        mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize(
+                R.dimen.keyguard_indication_margin_bottom_ambient);
         updateCameraVisibility();
         mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
         mUnlockMethodCache.addListener(this);
@@ -303,11 +312,13 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        int indicationBottomMargin = getResources().getDimensionPixelSize(
+        mIndicationBottomMargin = getResources().getDimensionPixelSize(
                 R.dimen.keyguard_indication_margin_bottom);
+        mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize(
+                R.dimen.keyguard_indication_margin_bottom_ambient);
         MarginLayoutParams mlp = (MarginLayoutParams) mIndicationArea.getLayoutParams();
-        if (mlp.bottomMargin != indicationBottomMargin) {
-            mlp.bottomMargin = indicationBottomMargin;
+        if (mlp.bottomMargin != mIndicationBottomMargin) {
+            mlp.bottomMargin = mIndicationBottomMargin;
             mIndicationArea.setLayoutParams(mlp);
         }
 
@@ -543,6 +554,22 @@
         }
     }
 
+    public void setDarkAmount(float darkAmount) {
+        if (darkAmount == mDarkAmount) {
+            return;
+        }
+        mDarkAmount = darkAmount;
+        // Let's randomize the bottom margin every time we wake up to avoid burn-in.
+        if (darkAmount == 0) {
+            mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize(
+                    R.dimen.keyguard_indication_margin_bottom_ambient)
+                    + (int) (Math.random() * mIndicationText.getTextSize());
+        }
+        mIndicationArea.setAlpha(MathUtils.lerp(1f, 0.7f, darkAmount));
+        mIndicationArea.setTranslationY(MathUtils.lerp(0,
+                mIndicationBottomMargin - mIndicationBottomMarginAmbient, darkAmount));
+    }
+
     private static boolean isSuccessfulLaunch(int result) {
         return result == ActivityManager.START_SUCCESS
                 || result == ActivityManager.START_DELIVERED_TO_TOP
@@ -687,11 +714,6 @@
         if (mRightAffordanceView.getVisibility() == View.VISIBLE) {
             startFinishDozeAnimationElement(mRightAffordanceView, delay);
         }
-        mIndicationArea.setAlpha(0f);
-        mIndicationArea.animate()
-                .alpha(1f)
-                .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
-                .setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
     }
 
     private void startFinishDozeAnimationElement(View element, long delay) {
@@ -815,6 +837,22 @@
         }
     }
 
+    public void dozeTimeTick() {
+        if (mDarkAmount == 1) {
+            // Move indication every minute to avoid burn-in
+            final int dozeTranslation = mIndicationBottomMargin - mIndicationBottomMarginAmbient;
+            mIndicationArea.setTranslationY(dozeTranslation + (float) Math.random() * 5);
+        }
+    }
+
+    public void setBurnInXOffset(int burnInXOffset) {
+        if (mBurnInXOffset == burnInXOffset) {
+            return;
+        }
+        mBurnInXOffset = burnInXOffset;
+        mIndicationArea.setTranslationX(burnInXOffset);
+    }
+
     private class DefaultLeftButton implements IntentButton {
 
         private IconState mIconState = new IconState();
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/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 34486db..264f574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -250,7 +250,7 @@
                 }
                 break;
             case STATE_FACE_UNLOCK:
-                iconRes = R.drawable.ic_account_circle;
+                iconRes = R.drawable.ic_face_unlock;
                 break;
             case STATE_FINGERPRINT:
                 // If screen is off and device asleep, use the draw on animation so the first frame
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/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 6f636aa..4faa84a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -19,15 +19,14 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
-import android.view.ViewConfiguration;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
@@ -37,6 +36,7 @@
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
 import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.tuner.TunerService;
 
@@ -72,6 +72,7 @@
     private NavigationBarView mNavigationBarView;
     private boolean mIsVertical;
 
+    private final QuickScrubController mQuickScrubController;
     private final int mScrollTouchSlop;
     private final Matrix mTransformGlobalMatrix = new Matrix();
     private final Matrix mTransformLocalMatrix = new Matrix();
@@ -89,6 +90,7 @@
         mContext = context;
         Resources r = context.getResources();
         mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
+        mQuickScrubController = new QuickScrubController(context);
         Dependency.get(TunerService.class).addTunable(this, KEY_DOCK_WINDOW_GESTURE);
     }
 
@@ -101,10 +103,12 @@
         mRecentsComponent = recentsComponent;
         mDivider = divider;
         mNavigationBarView = navigationBarView;
+        mQuickScrubController.setComponents(mNavigationBarView);
     }
 
     public void setBarState(boolean isVertical, boolean isRTL) {
         mIsVertical = isVertical;
+        mQuickScrubController.setBarState(isVertical, isRTL);
     }
 
     private boolean proxyMotionEvents(MotionEvent event) {
@@ -126,7 +130,6 @@
 
     public boolean onInterceptTouchEvent(MotionEvent event) {
         int action = event.getAction();
-        boolean result = false;
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
                 mTouchDownX = (int) event.getX();
@@ -137,24 +140,26 @@
                 mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
                 break;
             }
-            case MotionEvent.ACTION_MOVE: {
-                int x = (int) event.getX();
-                int y = (int) event.getY();
-                int xDiff = Math.abs(x - mTouchDownX);
-                int yDiff = Math.abs(y - mTouchDownY);
-                boolean exceededTouchSlop = xDiff > mScrollTouchSlop && xDiff > yDiff
-                        || yDiff > mScrollTouchSlop && yDiff > xDiff;
-                if (exceededTouchSlop) {
-                    result = true;
-                }
-                break;
-            }
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                break;
         }
-        proxyMotionEvents(event);
-        return result || (mDockWindowEnabled && interceptDockWindowEvent(event));
+        if (!mQuickScrubController.onInterceptTouchEvent(event)) {
+            proxyMotionEvents(event);
+            return false;
+        }
+        return (mDockWindowEnabled && interceptDockWindowEvent(event));
+    }
+
+    public void onDraw(Canvas canvas) {
+        if (mOverviewEventSender.getProxy() != null) {
+            mQuickScrubController.onDraw(canvas);
+        }
+    }
+
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        mQuickScrubController.onLayout(changed, left, top, right, bottom);
+    }
+
+    public void onDarkIntensityChange(float intensity) {
+        mQuickScrubController.onDarkIntensityChange(intensity);
     }
 
     private boolean interceptDockWindowEvent(MotionEvent event) {
@@ -294,7 +299,7 @@
     }
 
     public boolean onTouchEvent(MotionEvent event) {
-        boolean result = proxyMotionEvents(event);
+        boolean result = mQuickScrubController.onTouchEvent(event) || proxyMotionEvents(event);
         if (mDockWindowEnabled) {
             result |= handleDockWindowEvent(event);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index bd6421c..b113675 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -138,6 +138,7 @@
         if (mAutoDim) {
             applyLightsOut(false, true);
         }
+        mView.onDarkIntensityChange(darkIntensity);
     }
 
     private final View.OnTouchListener mLightsOutListener = new View.OnTouchListener() {
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 006a85b..059ce92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -26,6 +26,7 @@
 import android.app.StatusBarManager;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -625,6 +626,24 @@
         updateRotatedViews();
     }
 
+    public void onDarkIntensityChange(float intensity) {
+        if (mGestureHelper != null) {
+            mGestureHelper.onDarkIntensityChange(intensity);
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        mGestureHelper.onDraw(canvas);
+        super.onDraw(canvas);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        mGestureHelper.onLayout(changed, left, top, right, bottom);
+    }
+
     private void updateRotatedViews() {
         mRotatedViews[Surface.ROTATION_0] =
                 mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 32675d3..0cc7f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -478,6 +478,7 @@
         }
         mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
         mNotificationStackScroller.setDarkShelfOffsetX(mClockPositionResult.clockX);
+        mKeyguardBottomArea.setBurnInXOffset(mClockPositionResult.clockX);
         requestScrollerTopPaddingUpdate(animate);
     }
 
@@ -2608,7 +2609,8 @@
 
     private void setDarkAmount(float amount) {
         mDarkAmount = amount;
-        mKeyguardStatusView.setDark(mDarkAmount);
+        mKeyguardStatusView.setDarkAmount(mDarkAmount);
+        mKeyguardBottomArea.setDarkAmount(mDarkAmount);
         positionClockAndNotifications();
     }
 
@@ -2630,8 +2632,9 @@
         }
     }
 
-    public void refreshTime() {
+    public void dozeTimeTick() {
         mKeyguardStatusView.refreshTime();
+        mKeyguardBottomArea.dozeTimeTick();
         if (mDarkAmount > 0) {
             positionClockAndNotifications();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
new file mode 100644
index 0000000..9f8a7ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
@@ -0,0 +1,370 @@
+/*
+ * 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.phone;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.view.Display;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.support.annotation.DimenRes;
+import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
+import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
+import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.utilities.Utilities;
+
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
+
+/**
+ * Class to detect gestures on the navigation bar and implement quick scrub and switch.
+ */
+public class QuickScrubController extends GestureDetector.SimpleOnGestureListener implements
+        GestureHelper {
+
+    private static final String TAG = "QuickScrubController";
+    private static final int QUICK_SWITCH_FLING_VELOCITY = 0;
+    private static final int ANIM_DURATION_MS = 200;
+    private static final long LONG_PRESS_DELAY_MS = 150;
+
+    /**
+     * For quick step, set a damping value to allow the button to stick closer its origin position
+     * when dragging before quick scrub is active.
+     */
+    private static final int SWITCH_STICKINESS = 4;
+
+    private NavigationBarView mNavigationBarView;
+    private GestureDetector mGestureDetector;
+
+    private boolean mDraggingActive;
+    private boolean mQuickScrubActive;
+    private float mDownOffset;
+    private float mTranslation;
+    private int mTouchDownX;
+    private int mTouchDownY;
+    private boolean mDragPositive;
+    private boolean mIsVertical;
+    private boolean mIsRTL;
+    private float mMaxTrackPaintAlpha;
+
+    private final Handler mHandler = new Handler();
+    private final Interpolator mQuickScrubEndInterpolator = new DecelerateInterpolator();
+    private final Rect mTrackRect = new Rect();
+    private final Rect mHomeButtonRect = new Rect();
+    private final Paint mTrackPaint = new Paint();
+    private final int mScrollTouchSlop;
+    private final OverviewProxyService mOverviewEventSender;
+    private final Display mDisplay;
+    private final int mTrackThickness;
+    private final int mTrackPadding;
+    private final ValueAnimator mTrackAnimator;
+    private final ValueAnimator mButtonAnimator;
+    private final AnimatorSet mQuickScrubEndAnimator;
+    private final Context mContext;
+
+    private final AnimatorUpdateListener mTrackAnimatorListener = valueAnimator -> {
+        mTrackPaint.setAlpha(Math.round((float) valueAnimator.getAnimatedValue() * 255));
+        mNavigationBarView.invalidate();
+    };
+
+    private final AnimatorUpdateListener mButtonTranslationListener = animator -> {
+        int pos = (int) animator.getAnimatedValue();
+        if (!mQuickScrubActive) {
+            pos = mDragPositive ? Math.min((int) mTranslation, pos) : Math.max((int) mTranslation, pos);
+        }
+        final View homeView = mNavigationBarView.getHomeButton().getCurrentView();
+        if (mIsVertical) {
+            homeView.setTranslationY(pos);
+        } else {
+            homeView.setTranslationX(pos);
+        }
+    };
+
+    private AnimatorListenerAdapter mQuickScrubEndListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mNavigationBarView.getHomeButton().setClickable(true);
+            mQuickScrubActive = false;
+            mTranslation = 0;
+        }
+    };
+
+    private Runnable mLongPressRunnable = this::startQuickScrub;
+
+    private final GestureDetector.SimpleOnGestureListener mGestureListener =
+        new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) {
+                if (mQuickScrubActive) {
+                    return false;
+                }
+                float velocityX = mIsRTL ? -velX : velX;
+                float absVelY = Math.abs(velY);
+                final boolean isValidFling = velocityX > QUICK_SWITCH_FLING_VELOCITY &&
+                        mIsVertical ? (absVelY > velocityX) : (velocityX > absVelY);
+                if (isValidFling) {
+                    mDraggingActive = false;
+                    mButtonAnimator.setIntValues((int) mTranslation, 0);
+                    mButtonAnimator.start();
+                    mHandler.removeCallbacks(mLongPressRunnable);
+                    try {
+                        final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
+                        overviewProxy.onQuickSwitch();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Failed to send start of quick switch.", e);
+                    }
+                }
+                return true;
+            }
+        };
+
+    public QuickScrubController(Context context) {
+        mContext = context;
+        mScrollTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mDisplay = ((WindowManager) context.getSystemService(
+                Context.WINDOW_SERVICE)).getDefaultDisplay();
+        mOverviewEventSender = Dependency.get(OverviewProxyService.class);
+        mGestureDetector = new GestureDetector(mContext, mGestureListener);
+        mTrackThickness = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_thickness);
+        mTrackPadding = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_edge_padding);
+
+        mTrackAnimator = ObjectAnimator.ofFloat();
+        mTrackAnimator.addUpdateListener(mTrackAnimatorListener);
+        mButtonAnimator = ObjectAnimator.ofInt();
+        mButtonAnimator.addUpdateListener(mButtonTranslationListener);
+        mQuickScrubEndAnimator = new AnimatorSet();
+        mQuickScrubEndAnimator.playTogether(mTrackAnimator, mButtonAnimator);
+        mQuickScrubEndAnimator.setDuration(ANIM_DURATION_MS);
+        mQuickScrubEndAnimator.addListener(mQuickScrubEndListener);
+        mQuickScrubEndAnimator.setInterpolator(mQuickScrubEndInterpolator);
+    }
+
+    public void setComponents(NavigationBarView navigationBarView) {
+        mNavigationBarView = navigationBarView;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
+        final ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
+        if (overviewProxy == null) {
+            homeButton.setDelayTouchFeedback(false);
+            return false;
+        }
+        mGestureDetector.onTouchEvent(event);
+        int action = event.getAction();
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN: {
+                int x = (int) event.getX();
+                int y = (int) event.getY();
+                if (mHomeButtonRect.contains(x, y)) {
+                    mTouchDownX = x;
+                    mTouchDownY = y;
+                    homeButton.setDelayTouchFeedback(true);
+                    mHandler.postDelayed(mLongPressRunnable, LONG_PRESS_DELAY_MS);
+                } else {
+                    mTouchDownX = mTouchDownY = -1;
+                }
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                if (mTouchDownX != -1) {
+                    int x = (int) event.getX();
+                    int y = (int) event.getY();
+                    int xDiff = Math.abs(x - mTouchDownX);
+                    int yDiff = Math.abs(y - mTouchDownY);
+                    boolean exceededTouchSlop;
+                    int pos, touchDown, offset, trackSize;
+                    if (mIsVertical) {
+                        exceededTouchSlop = yDiff > mScrollTouchSlop && yDiff > xDiff;
+                        pos = y;
+                        touchDown = mTouchDownY;
+                        offset = pos - mTrackRect.top;
+                        trackSize = mTrackRect.height();
+                    } else {
+                        exceededTouchSlop = xDiff > mScrollTouchSlop && xDiff > yDiff;
+                        pos = x;
+                        touchDown = mTouchDownX;
+                        offset = pos - mTrackRect.left;
+                        trackSize = mTrackRect.width();
+                    }
+                    if (!mDragPositive) {
+                        offset -= mIsVertical ? mTrackRect.height() : mTrackRect.width();
+                    }
+
+                    // Control the button movement
+                    if (!mDraggingActive && exceededTouchSlop) {
+                        boolean allowDrag = !mDragPositive
+                                ? offset < 0 && pos < touchDown : offset >= 0 && pos > touchDown;
+                        if (allowDrag) {
+                            mDownOffset = offset;
+                            homeButton.setClickable(false);
+                            mDraggingActive = true;
+                        }
+                    }
+                    if (mDraggingActive && (mDragPositive && offset >= 0
+                            || !mDragPositive && offset <= 0)) {
+                        float scrubFraction =
+                                Utilities.clamp(Math.abs(offset) * 1f / trackSize, 0, 1);
+                        mTranslation = !mDragPositive
+                            ? Utilities.clamp(offset - mDownOffset, -trackSize, 0)
+                            : Utilities.clamp(offset - mDownOffset, 0, trackSize);
+                        if (mQuickScrubActive) {
+                            try {
+                                overviewProxy.onQuickScrubProgress(scrubFraction);
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "Failed to send progress of quick scrub.", e);
+                            }
+                        } else {
+                            mTranslation /= SWITCH_STICKINESS;
+                        }
+                        if (mIsVertical) {
+                            homeButton.getCurrentView().setTranslationY(mTranslation);
+                        } else {
+                            homeButton.getCurrentView().setTranslationX(mTranslation);
+                        }
+                    }
+                }
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                endQuickScrub();
+                break;
+        }
+        return mDraggingActive || mQuickScrubActive;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        canvas.drawRect(mTrackRect, mTrackPaint);
+    }
+
+    @Override
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final int width = right - left;
+        final int height = bottom - top;
+        final int x1, x2, y1, y2;
+        if (mIsVertical) {
+            x1 = (width - mTrackThickness) / 2;
+            x2 = x1 + mTrackThickness;
+            y1 = mDragPositive ? height / 2 : mTrackPadding;
+            y2 = y1 + height / 2 - mTrackPadding;
+        } else {
+            y1 = (height - mTrackThickness) / 2;
+            y2 = y1 + mTrackThickness;
+            x1 = mDragPositive ? width / 2 : mTrackPadding;
+            x2 = x1 + width / 2 - mTrackPadding;
+        }
+        mTrackRect.set(x1, y1, x2, y2);
+
+        // Get the touch rect of the home button location
+        View homeView = mNavigationBarView.getHomeButton().getCurrentView();
+        int[] globalHomePos = homeView.getLocationOnScreen();
+        int[] globalNavBarPos = mNavigationBarView.getLocationOnScreen();
+        int homeX = globalHomePos[0] - globalNavBarPos[0];
+        int homeY = globalHomePos[1] - globalNavBarPos[1];
+        mHomeButtonRect.set(homeX, homeY, homeX + homeView.getMeasuredWidth(),
+                homeY + homeView.getMeasuredHeight());
+    }
+
+    @Override
+    public void onDarkIntensityChange(float intensity) {
+        if (intensity == 0) {
+            mTrackPaint.setColor(mContext.getColor(R.color.quick_step_track_background_light));
+        } else if (intensity == 1) {
+            mTrackPaint.setColor(mContext.getColor(R.color.quick_step_track_background_dark));
+        }
+        mMaxTrackPaintAlpha = mTrackPaint.getAlpha() * 1f / 255;
+        mTrackPaint.setAlpha(0);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            endQuickScrub();
+        }
+        return false;
+    }
+
+    @Override
+    public void setBarState(boolean isVertical, boolean isRTL) {
+        mIsVertical = isVertical;
+        mIsRTL = isRTL;
+        try {
+            int navbarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition();
+            mDragPositive = navbarPos == NAV_BAR_LEFT || navbarPos == NAV_BAR_BOTTOM;
+            if (isRTL) {
+                mDragPositive = !mDragPositive;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get nav bar position.", e);
+        }
+    }
+
+    private void startQuickScrub() {
+        if (!mQuickScrubActive) {
+            mQuickScrubActive = true;
+            mTrackAnimator.setFloatValues(0, mMaxTrackPaintAlpha);
+            mTrackAnimator.start();
+            try {
+                mOverviewEventSender.getProxy().onQuickScrubStart();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to send start of quick scrub.", e);
+            }
+        }
+    }
+
+    private void endQuickScrub() {
+        mHandler.removeCallbacks(mLongPressRunnable);
+        if (mDraggingActive || mQuickScrubActive) {
+            mButtonAnimator.setIntValues((int) mTranslation, 0);
+            mTrackAnimator.setFloatValues(mTrackPaint.getAlpha() * 1f / 255, 0);
+            mQuickScrubEndAnimator.start();
+            try {
+                mOverviewEventSender.getProxy().onQuickScrubEnd();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to send end of quick scrub.", e);
+            }
+        }
+        mDraggingActive = false;
+    }
+
+    private int getDimensionPixelSize(Context context, @DimenRes int resId) {
+        return context.getResources().getDimensionPixelSize(resId);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 2da1e4d..af65a86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1596,7 +1596,7 @@
 
         final boolean hasArtwork = artworkDrawable != null;
 
-        if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
+        if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) && !mDozing
                 && (mState != StatusBarState.SHADE || allowWhenShade)
                 && mFingerprintUnlockController.getMode()
                         != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
@@ -1657,15 +1657,16 @@
                 }
             }
         } else {
-            // need to hide the album art, either because we are unlocked or because
-            // the metadata isn't there to support it
+            // need to hide the album art, either because we are unlocked, on AOD
+            // or because the metadata isn't there to support it
             if (mBackdrop.getVisibility() != View.GONE) {
                 if (DEBUG_MEDIA) {
                     Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork");
                 }
+                boolean cannotAnimateDoze = mDozing && !ScrimState.AOD.getAnimateChange();
                 if (mFingerprintUnlockController.getMode()
                         == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
-                        || hideBecauseOccluded) {
+                        || hideBecauseOccluded || cannotAnimateDoze) {
 
                     // We are unlocking directly - no animation!
                     mBackdrop.setVisibility(View.GONE);
@@ -4644,7 +4645,7 @@
 
         @Override
         public void dozeTimeTick() {
-            mNotificationPanel.refreshTime();
+            mNotificationPanel.dozeTimeTick();
         }
 
         @Override
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/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
index cc7943b..a2bec98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
@@ -26,9 +26,11 @@
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
 import android.view.DisplayListCanvas;
 import android.view.RenderNodeAnimator;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.animation.Interpolator;
 
 import com.android.systemui.Interpolators;
@@ -56,14 +58,17 @@
     private float mGlowAlpha = 0f;
     private float mGlowScale = 1f;
     private boolean mPressed;
+    private boolean mVisible;
     private boolean mDrawingHardwareGlow;
     private int mMaxWidth;
     private boolean mLastDark;
     private boolean mDark;
+    private boolean mDelayTouchFeedback;
 
     private final Interpolator mInterpolator = new LogInterpolator();
     private boolean mSupportHardware;
     private final View mTargetView;
+    private final Handler mHandler = new Handler();
 
     private final HashSet<Animator> mRunningAnimations = new HashSet<>();
     private final ArrayList<Animator> mTmpArray = new ArrayList<>();
@@ -77,6 +82,10 @@
         mDark = darkIntensity >= 0.5f;
     }
 
+    public void setDelayTouchFeedback(boolean delay) {
+        mDelayTouchFeedback = delay;
+    }
+
     private Paint getRipplePaint() {
         if (mRipplePaint == null) {
             mRipplePaint = new Paint();
@@ -211,7 +220,16 @@
         }
     }
 
+    /**
+     * Abort the ripple while it is delayed and before shown used only when setShouldDelayStartTouch
+     * is enabled.
+     */
+    public void abortDelayedRipple() {
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
     private void cancelAnimations() {
+        mVisible = false;
         mTmpArray.addAll(mRunningAnimations);
         int size = mTmpArray.size();
         for (int i = 0; i < size; i++) {
@@ -220,11 +238,21 @@
         }
         mTmpArray.clear();
         mRunningAnimations.clear();
+        mHandler.removeCallbacksAndMessages(null);
     }
 
     private void setPressedSoftware(boolean pressed) {
         if (pressed) {
-            enterSoftware();
+            if (mDelayTouchFeedback) {
+                if (mRunningAnimations.isEmpty()) {
+                    mHandler.removeCallbacksAndMessages(null);
+                    mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout());
+                } else if (mVisible) {
+                    enterSoftware();
+                }
+            } else {
+                enterSoftware();
+            }
         } else {
             exitSoftware();
         }
@@ -232,6 +260,7 @@
 
     private void enterSoftware() {
         cancelAnimations();
+        mVisible = true;
         mGlowAlpha = getMaxGlowAlpha();
         ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale",
                 0f, GLOW_MAX_SCALE_FACTOR);
@@ -240,6 +269,12 @@
         scaleAnimator.addListener(mAnimatorListener);
         scaleAnimator.start();
         mRunningAnimations.add(scaleAnimator);
+
+        // With the delay, it could eventually animate the enter animation with no pressed state,
+        // then immediately show the exit animation. If this is skipped there will be no ripple.
+        if (mDelayTouchFeedback && !mPressed) {
+            exitSoftware();
+        }
     }
 
     private void exitSoftware() {
@@ -253,7 +288,16 @@
 
     private void setPressedHardware(boolean pressed) {
         if (pressed) {
-            enterHardware();
+            if (mDelayTouchFeedback) {
+                if (mRunningAnimations.isEmpty()) {
+                    mHandler.removeCallbacksAndMessages(null);
+                    mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout());
+                } else if (mVisible) {
+                    enterHardware();
+                }
+            } else {
+                enterHardware();
+            }
         } else {
             exitHardware();
         }
@@ -302,6 +346,7 @@
 
     private void enterHardware() {
         cancelAnimations();
+        mVisible = true;
         mDrawingHardwareGlow = true;
         setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2));
         final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(),
@@ -343,6 +388,12 @@
         mRunningAnimations.add(endAnim);
 
         invalidateSelf();
+
+        // With the delay, it could eventually animate the enter animation with no pressed state,
+        // then immediately show the exit animation. If this is skipped there will be no ripple.
+        if (mDelayTouchFeedback && !mPressed) {
+            exitHardware();
+        }
     }
 
     private void exitHardware() {
@@ -366,6 +417,7 @@
         public void onAnimationEnd(Animator animation) {
             mRunningAnimations.remove(animation);
             if (mRunningAnimations.isEmpty() && !mPressed) {
+                mVisible = false;
                 mDrawingHardwareGlow = false;
                 invalidateSelf();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 0501771..077c6c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -284,6 +284,7 @@
     @Override
     public void abortCurrentGesture() {
         setPressed(false);
+        mRipple.abortDelayedRipple();
         mGestureAborted = true;
     }
 
@@ -301,6 +302,11 @@
     }
 
     @Override
+    public void setDelayTouchFeedback(boolean shouldDelay) {
+        mRipple.setDelayTouchFeedback(shouldDelay);
+    }
+
+    @Override
     public void setVertical(boolean vertical) {
         //no op
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
new file mode 100644
index 0000000..1dcdf63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -0,0 +1,63 @@
+package com.android.systemui.statusbar.policy;
+
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.android.systemui.R;
+
+/** View which displays smart reply buttons in notifications. */
+public class SmartReplyView extends LinearLayout {
+
+    private static final String TAG = "SmartReplyView";
+
+    public SmartReplyView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setRepliesFromRemoteInput(RemoteInput remoteInput, PendingIntent pendingIntent) {
+        removeAllViews();
+        if (remoteInput != null && pendingIntent != null) {
+            CharSequence[] choices = remoteInput.getChoices();
+            if (choices != null) {
+                for (CharSequence choice : choices) {
+                    Button replyButton = inflateReplyButton(
+                            getContext(), this, choice, remoteInput, pendingIntent);
+                    addView(replyButton);
+                }
+            }
+        }
+    }
+
+    public static SmartReplyView inflate(Context context, ViewGroup root) {
+        return (SmartReplyView)
+                LayoutInflater.from(context).inflate(R.layout.smart_reply_view, root, false);
+    }
+
+    private static Button inflateReplyButton(Context context, ViewGroup root, CharSequence choice,
+            RemoteInput remoteInput, PendingIntent pendingIntent) {
+        Button b = (Button) LayoutInflater.from(context).inflate(
+                R.layout.smart_reply_button, root, false);
+        b.setText(choice);
+        b.setOnClickListener(view -> {
+            Bundle results = new Bundle();
+            results.putString(remoteInput.getResultKey(), choice.toString());
+            Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            RemoteInput.addResultsToIntent(new RemoteInput[]{remoteInput}, intent, results);
+            try {
+                pendingIntent.send(context, 0, intent);
+            } catch (PendingIntent.CanceledException e) {
+                Log.w(TAG, "Unable to send smart reply", e);
+            }
+        });
+        return b;
+    }
+}
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/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
index 0a68389..f9ec3f92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
@@ -32,11 +32,14 @@
 
 import android.app.ActivityManager;
 import android.app.Notification;
+import android.app.NotificationManager;
 import android.content.Context;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationRankingUpdate;
 import android.service.notification.StatusBarNotification;
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -120,6 +123,23 @@
         }
     }
 
+    private void setUserSentiment(String key, int sentiment) {
+        doAnswer(invocationOnMock -> {
+            NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking)
+                    invocationOnMock.getArguments()[1];
+            ranking.populate(
+                    key,
+                    0,
+                    false,
+                    0,
+                    0,
+                    NotificationManager.IMPORTANCE_DEFAULT,
+                    null, null,
+                    null, null, null, true, sentiment);
+            return true;
+        }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
+    }
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -158,6 +178,8 @@
 
         mEntryManager = new TestableNotificationEntryManager(mContext, mBarService);
         mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mCallback, mHeadsUpManager);
+
+        setUserSentiment(mEntry.key, NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL);
     }
 
     @Test
@@ -196,6 +218,8 @@
 
         assertEquals(mEntryManager.getNotificationData().get(mSbn.getKey()), entry);
         assertNotNull(entry.row);
+        assertEquals(mEntry.userSentiment,
+                NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL);
     }
 
     @Test
@@ -204,6 +228,8 @@
 
         mEntryManager.getNotificationData().add(mEntry);
 
+        setUserSentiment(mEntry.key, NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
+
         mHandler.post(() -> {
             mEntryManager.updateNotification(mSbn, mRankingMap);
         });
@@ -219,6 +245,8 @@
         verify(mForegroundServiceController).updateNotification(eq(mSbn), anyInt());
         verify(mCallback).onNotificationUpdated(mSbn);
         assertNotNull(mEntry.row);
+        assertEquals(mEntry.userSentiment,
+                NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
     }
 
     @Test
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/keystore/KeyDerivationParameters.aidl b/packages/overlays/DisplayCutoutEmulationOverlay/res/values/strings.xml
similarity index 60%
copy from core/java/android/security/keystore/KeyDerivationParameters.aidl
copy to packages/overlays/DisplayCutoutEmulationOverlay/res/values/strings.xml
index 92316e5..5d5c425 100644
--- a/core/java/android/security/keystore/KeyDerivationParameters.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.keystore;
+    <string name="display_cutout_emulation_overlay">Display Cutout Emulation</string>
 
-/* @hide */
-parcelable KeyDerivationParameters;
+</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 6f52692..fc6058c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -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/BackupPolicyEnforcer.java b/services/backup/java/com/android/server/backup/BackupPolicyEnforcer.java
new file mode 100644
index 0000000..158084a
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/BackupPolicyEnforcer.java
@@ -0,0 +1,25 @@
+package com.android.server.backup;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A helper class to decouple this service from {@link DevicePolicyManager} in order to improve
+ * testability.
+ */
+@VisibleForTesting
+public class BackupPolicyEnforcer {
+    private DevicePolicyManager mDevicePolicyManager;
+
+    public BackupPolicyEnforcer(Context context) {
+        mDevicePolicyManager =
+                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+    }
+
+    public ComponentName getMandatoryBackupTransport() {
+        return mDevicePolicyManager.getMandatoryBackupTransport();
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 03591a8..5188910 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -38,6 +38,7 @@
 import android.app.IActivityManager;
 import android.app.IBackupAgent;
 import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
 import android.app.backup.BackupManager;
 import android.app.backup.BackupManagerMonitor;
 import android.app.backup.FullBackup;
@@ -207,6 +208,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 +287,9 @@
 
     private final BackupPasswordManager mBackupPasswordManager;
 
+    // Time when we post the transport registration operation
+    private final long mRegisterTransportsRequestedTime;
+
     @GuardedBy("mPendingRestores")
     private boolean mIsRestoreInProgress;
     @GuardedBy("mPendingRestores")
@@ -674,6 +682,8 @@
     @GuardedBy("mQueueLock")
     private ArrayList<FullBackupEntry> mFullBackupQueue;
 
+    private BackupPolicyEnforcer mBackupPolicyEnforcer;
+
     // Utility: build a new random integer token. The low bits are the ordinal of the
     // operation for near-time uniqueness, and the upper bits are random for app-
     // side unpredictability.
@@ -735,6 +745,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 +762,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,15 +864,19 @@
         }
 
         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*");
+
+        mBackupPolicyEnforcer = new BackupPolicyEnforcer(context);
     }
 
     private void initPackageTracking() {
@@ -1151,39 +1167,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() {
@@ -2774,6 +2779,10 @@
     public void setBackupEnabled(boolean enable) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "setBackupEnabled");
+        if (!enable && mBackupPolicyEnforcer.getMandatoryBackupTransport() != null) {
+            Slog.w(TAG, "Cannot disable backups when the mandatory backups policy is active.");
+            return;
+        }
 
         Slog.i(TAG, "Backup enabled => " + enable);
 
@@ -2891,14 +2900,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,10 +3012,18 @@
 
     /** Selects transport {@code transportName} and returns previous selected transport. */
     @Override
+    @Deprecated
+    @Nullable
     public String selectBackupTransport(String transportName) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BACKUP, "selectBackupTransport");
 
+        if (!isAllowedByMandatoryBackupTransportPolicy(transportName)) {
+            // Don't change the transport if it is not allowed.
+            Slog.w(TAG, "Failed to select transport - disallowed by device owner policy.");
+            return mTransportManager.getCurrentTransportName();
+        }
+
         final long oldId = Binder.clearCallingIdentity();
         try {
             String previousTransportName = mTransportManager.selectTransport(transportName);
@@ -3021,10 +3038,20 @@
 
     @Override
     public void selectBackupTransportAsync(
-            ComponentName transportComponent, ISelectBackupTransportCallback listener) {
+            ComponentName transportComponent, @Nullable ISelectBackupTransportCallback listener) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BACKUP, "selectBackupTransportAsync");
-
+        if (!isAllowedByMandatoryBackupTransportPolicy(transportComponent)) {
+            try {
+                if (listener != null) {
+                    Slog.w(TAG, "Failed to select transport - disallowed by device owner policy.");
+                    listener.onFailure(BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "ISelectBackupTransportCallback listener not available");
+            }
+            return;
+        }
         final long oldId = Binder.clearCallingIdentity();
         try {
             String transportString = transportComponent.flattenToShortString();
@@ -3046,10 +3073,12 @@
                         }
 
                         try {
-                            if (transportName != null) {
-                                listener.onSuccess(transportName);
-                            } else {
-                                listener.onFailure(result);
+                            if (listener != null) {
+                                if (transportName != null) {
+                                    listener.onSuccess(transportName);
+                                } else {
+                                    listener.onFailure(result);
+                                }
                             }
                         } catch (RemoteException e) {
                             Slog.e(TAG, "ISelectBackupTransportCallback listener not available");
@@ -3060,6 +3089,38 @@
         }
     }
 
+    /**
+     * Returns if the specified transport can be set as the current transport without violating the
+     * mandatory backup transport policy.
+     */
+    private boolean isAllowedByMandatoryBackupTransportPolicy(String transportName) {
+        ComponentName mandatoryBackupTransport = mBackupPolicyEnforcer.getMandatoryBackupTransport();
+        if (mandatoryBackupTransport == null) {
+            return true;
+        }
+        final String mandatoryBackupTransportName;
+        try {
+            mandatoryBackupTransportName =
+                    mTransportManager.getTransportName(mandatoryBackupTransport);
+        } catch (TransportNotRegisteredException e) {
+            Slog.e(TAG, "mandatory backup transport not registered!");
+            return false;
+        }
+        return TextUtils.equals(mandatoryBackupTransportName, transportName);
+    }
+
+    /**
+     * Returns if the specified transport can be set as the current transport without violating the
+     * mandatory backup transport policy.
+     */
+    private boolean isAllowedByMandatoryBackupTransportPolicy(ComponentName transport) {
+        ComponentName mandatoryBackupTransport = mBackupPolicyEnforcer.getMandatoryBackupTransport();
+        if (mandatoryBackupTransport == null) {
+            return true;
+        }
+        return mandatoryBackupTransport.equals(transport);
+    }
+
     private void updateStateForTransport(String newTransportName) {
         // Publish the name change
         Settings.Secure.putString(mContext.getContentResolver(),
@@ -3079,6 +3140,7 @@
                 mCurrentToken = 0;
                 Slog.w(TAG, "Transport " + newTransportName + " not available: current token = 0");
             }
+            mTransportManager.disposeOfTransportClient(transportClient, callerLogString);
         } else {
             Slog.w(TAG, "Transport " + newTransportName + " not registered: current token = 0");
             // The named transport isn't registered, so we can't know what its current dataset token
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 34b8935..30fd25a 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -16,93 +16,54 @@
 
 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.server.EventLogTags;
+import com.android.internal.util.Preconditions;
+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 +71,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 +173,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 +211,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 +238,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 +251,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 +338,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 +351,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 +371,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 +392,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 +436,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 +544,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 +551,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()";
@@ -686,29 +572,21 @@
             return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
         }
 
-        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");
+            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 +595,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..399f338 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java
@@ -29,12 +29,14 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.EventLog;
 import android.util.Log;
 
 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.TransportManager;
 
 import java.lang.annotation.Retention;
@@ -236,7 +238,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
@@ -419,10 +421,45 @@
     @GuardedBy("mStateLock")
     private void setStateLocked(@State int state, @Nullable IBackupTransport transport) {
         log(Log.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
+        onStateTransition(mState, state);
         mState = state;
         mTransport = transport;
     }
 
+    private void onStateTransition(int oldState, int newState) {
+        String transport = mTransportComponent.flattenToShortString();
+        int bound = transitionThroughState(oldState, newState, State.BOUND_AND_CONNECTING);
+        int connected = transitionThroughState(oldState, newState, State.CONNECTED);
+        if (bound != Transition.NO_TRANSITION) {
+            int value = (bound == Transition.UP) ? 1 : 0; // 1 is bound, 0 is not bound
+            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transport, value);
+        }
+        if (connected != Transition.NO_TRANSITION) {
+            int value = (connected == Transition.UP) ? 1 : 0; // 1 is connected, 0 is not connected
+            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_CONNECTION, transport, value);
+        }
+    }
+
+    /**
+     * Returns:
+     *
+     * <ul>
+     *   <li>{@link Transition#UP}, if oldState < stateReference <= newState
+     *   <li>{@link Transition#DOWN}, if oldState >= stateReference > newState
+     *   <li>{@link Transition#NO_TRANSITION}, otherwise
+     */
+    @Transition
+    private int transitionThroughState(
+            @State int oldState, @State int newState, @State int stateReference) {
+        if (oldState < stateReference && stateReference <= newState) {
+            return Transition.UP;
+        }
+        if (oldState >= stateReference && stateReference > newState) {
+            return Transition.DOWN;
+        }
+        return Transition.NO_TRANSITION;
+    }
+
     @GuardedBy("mStateLock")
     private void checkStateIntegrityLocked() {
         switch (mState) {
@@ -481,6 +518,14 @@
         // CharSequence time = DateFormat.format("yyyy-MM-dd HH:mm:ss", System.currentTimeMillis());
     }
 
+    @IntDef({Transition.DOWN, Transition.NO_TRANSITION, Transition.UP})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface Transition {
+        int DOWN = -1;
+        int NO_TRANSITION = 0;
+        int UP = 1;
+    }
+
     @IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED})
     @Retention(RetentionPolicy.SOURCE)
     private @interface State {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9fb2681..3369458 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -16,6 +16,7 @@
         ":installd_aidl",
         ":storaged_aidl",
         ":vold_aidl",
+        ":mediaupdateservice_aidl",
         "java/com/android/server/EventLogTags.logtags",
         "java/com/android/server/am/EventLogTags.logtags",
     ],
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index dcb0fab..04d292f 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -293,13 +293,10 @@
 
     private void updateBatteryWarningLevelLocked() {
         final ContentResolver resolver = mContext.getContentResolver();
-        final int defWarnLevel = mContext.getResources().getInteger(
+        int defWarnLevel = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_lowBatteryWarningLevel);
-        final int lowPowerModeTriggerLevel = Settings.Global.getInt(resolver,
+        mLowBatteryWarningLevel = Settings.Global.getInt(resolver,
                 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, defWarnLevel);
-
-        mLowBatteryWarningLevel = Math.min(defWarnLevel, lowPowerModeTriggerLevel);
-
         if (mLowBatteryWarningLevel == 0) {
             mLowBatteryWarningLevel = defWarnLevel;
         }
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/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 8361132..732ac66 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -133,6 +133,7 @@
 2846 full_backup_cancelled (Package|3),(Message|3)
 
 2850 backup_transport_lifecycle (Transport|3),(Bound|1|1)
+2851 backup_transport_connection (Transport|3),(Connected|1|1)
 
 
 # ---------------------------
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 02cfe3d..9d228c3 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -25,6 +25,7 @@
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.IIpSecService;
 import android.net.INetd;
 import android.net.IpSecAlgorithm;
@@ -62,7 +63,6 @@
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import libcore.io.IoUtils;
 
@@ -83,7 +83,7 @@
 
     private static final String NETD_SERVICE_NAME = "netd";
     private static final int[] DIRECTIONS =
-            new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
+            new int[] {IpSecManager.DIRECTION_OUT, IpSecManager.DIRECTION_IN};
 
     private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
     private static final int MAX_PORT_BIND_ATTEMPTS = 10;
@@ -104,10 +104,10 @@
     private final Context mContext;
 
     /**
-     * The next non-repeating global ID for tracking resources between users, this service,
-     * and kernel data structures. Accessing this variable is not thread safe, so it is
-     * only read or modified within blocks synchronized on IpSecService.this. We want to
-     * avoid -1 (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it).
+     * The next non-repeating global ID for tracking resources between users, this service, and
+     * kernel data structures. Accessing this variable is not thread safe, so it is only read or
+     * modified within blocks synchronized on IpSecService.this. We want to avoid -1
+     * (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it).
      */
     @GuardedBy("IpSecService.this")
     private int mNextResourceId = 1;
@@ -536,14 +536,14 @@
 
     private final class TransformRecord extends KernelResourceRecord {
         private final IpSecConfig mConfig;
-        private final SpiRecord[] mSpis;
+        private final SpiRecord mSpi;
         private final EncapSocketRecord mSocket;
 
         TransformRecord(
-                int resourceId, IpSecConfig config, SpiRecord[] spis, EncapSocketRecord socket) {
+                int resourceId, IpSecConfig config, SpiRecord spi, EncapSocketRecord socket) {
             super(resourceId);
             mConfig = config;
-            mSpis = spis;
+            mSpi = spi;
             mSocket = socket;
         }
 
@@ -551,29 +551,26 @@
             return mConfig;
         }
 
-        public SpiRecord getSpiRecord(int direction) {
-            return mSpis[direction];
+        public SpiRecord getSpiRecord() {
+            return mSpi;
         }
 
         /** always guarded by IpSecService#this */
         @Override
         public void freeUnderlyingResources() {
-            for (int direction : DIRECTIONS) {
-                int spi = mSpis[direction].getSpi();
-                try {
-                    mSrvConfig
-                            .getNetdInstance()
-                            .ipSecDeleteSecurityAssociation(
-                                    mResourceId,
-                                    direction,
-                                    mConfig.getLocalAddress(),
-                                    mConfig.getRemoteAddress(),
-                                    spi);
-                } catch (ServiceSpecificException e) {
-                    // FIXME: get the error code and throw is at an IOException from Errno Exception
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
-                }
+            int spi = mSpi.getSpi();
+            try {
+                mSrvConfig
+                        .getNetdInstance()
+                        .ipSecDeleteSecurityAssociation(
+                                mResourceId,
+                                mConfig.getSourceAddress(),
+                                mConfig.getDestinationAddress(),
+                                spi);
+            } catch (ServiceSpecificException e) {
+                // FIXME: get the error code and throw is at an IOException from Errno Exception
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
             }
 
             getResourceTracker().give();
@@ -597,10 +594,8 @@
                     .append(super.toString())
                     .append(", mSocket=")
                     .append(mSocket)
-                    .append(", mSpis[OUT].mResourceId=")
-                    .append(mSpis[IpSecTransform.DIRECTION_OUT].mResourceId)
-                    .append(", mSpis[IN].mResourceId=")
-                    .append(mSpis[IpSecTransform.DIRECTION_IN].mResourceId)
+                    .append(", mSpi.mResourceId=")
+                    .append(mSpi.mResourceId)
                     .append(", mConfig=")
                     .append(mConfig)
                     .append("}");
@@ -609,23 +604,16 @@
     }
 
     private final class SpiRecord extends KernelResourceRecord {
-        private final int mDirection;
-        private final String mLocalAddress;
-        private final String mRemoteAddress;
+        private final String mSourceAddress;
+        private final String mDestinationAddress;
         private int mSpi;
 
         private boolean mOwnedByTransform = false;
 
-        SpiRecord(
-                int resourceId,
-                int direction,
-                String localAddress,
-                String remoteAddress,
-                int spi) {
+        SpiRecord(int resourceId, String sourceAddress, String destinationAddress, int spi) {
             super(resourceId);
-            mDirection = direction;
-            mLocalAddress = localAddress;
-            mRemoteAddress = remoteAddress;
+            mSourceAddress = sourceAddress;
+            mDestinationAddress = destinationAddress;
             mSpi = spi;
         }
 
@@ -646,7 +634,7 @@
                 mSrvConfig
                         .getNetdInstance()
                         .ipSecDeleteSecurityAssociation(
-                                mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
+                                mResourceId, mSourceAddress, mDestinationAddress, mSpi);
             } catch (ServiceSpecificException e) {
                 // FIXME: get the error code and throw is at an IOException from Errno Exception
             } catch (RemoteException e) {
@@ -662,6 +650,10 @@
             return mSpi;
         }
 
+        public String getDestinationAddress() {
+            return mDestinationAddress;
+        }
+
         public void setOwnedByTransform() {
             if (mOwnedByTransform) {
                 // Programming error
@@ -689,12 +681,10 @@
                     .append(super.toString())
                     .append(", mSpi=")
                     .append(mSpi)
-                    .append(", mDirection=")
-                    .append(mDirection)
-                    .append(", mLocalAddress=")
-                    .append(mLocalAddress)
-                    .append(", mRemoteAddress=")
-                    .append(mRemoteAddress)
+                    .append(", mSourceAddress=")
+                    .append(mSourceAddress)
+                    .append(", mDestinationAddress=")
+                    .append(mDestinationAddress)
                     .append(", mOwnedByTransform=")
                     .append(mOwnedByTransform)
                     .append("}");
@@ -772,14 +762,17 @@
     /** @hide */
     @VisibleForTesting
     public IpSecService(Context context, IpSecServiceConfiguration config) {
-        this(context, config, (fd, uid) ->  {
-            try{
-                TrafficStats.setThreadStatsUid(uid);
-                TrafficStats.tagFileDescriptor(fd);
-            } finally {
-                TrafficStats.clearThreadStatsUid();
-            }
-        });
+        this(
+                context,
+                config,
+                (fd, uid) -> {
+                    try {
+                        TrafficStats.setThreadStatsUid(uid);
+                        TrafficStats.tagFileDescriptor(fd);
+                    } finally {
+                        TrafficStats.clearThreadStatsUid();
+                    }
+                });
     }
 
     /** @hide */
@@ -845,8 +838,8 @@
      */
     private static void checkDirection(int direction) {
         switch (direction) {
-            case IpSecTransform.DIRECTION_OUT:
-            case IpSecTransform.DIRECTION_IN:
+            case IpSecManager.DIRECTION_OUT:
+            case IpSecManager.DIRECTION_IN:
                 return;
         }
         throw new IllegalArgumentException("Invalid Direction: " + direction);
@@ -855,10 +848,8 @@
     /** Get a new SPI and maintain the reservation in the system server */
     @Override
     public synchronized IpSecSpiResponse allocateSecurityParameterIndex(
-            int direction, String remoteAddress, int requestedSpi, IBinder binder)
-            throws RemoteException {
-        checkDirection(direction);
-        checkInetAddress(remoteAddress);
+            String destinationAddress, int requestedSpi, IBinder binder) throws RemoteException {
+        checkInetAddress(destinationAddress);
         /* requestedSpi can be anything in the int range, so no check is needed. */
         checkNotNull(binder, "Null Binder passed to allocateSecurityParameterIndex");
 
@@ -866,28 +857,21 @@
         final int resourceId = mNextResourceId++;
 
         int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
-        String localAddress = "";
-
         try {
             if (!userRecord.mSpiQuotaTracker.isAvailable()) {
                 return new IpSecSpiResponse(
                         IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
             }
+
             spi =
                     mSrvConfig
                             .getNetdInstance()
-                            .ipSecAllocateSpi(
-                                    resourceId,
-                                    direction,
-                                    localAddress,
-                                    remoteAddress,
-                                    requestedSpi);
+                            .ipSecAllocateSpi(resourceId, "", destinationAddress, requestedSpi);
             Log.d(TAG, "Allocated SPI " + spi);
             userRecord.mSpiRecords.put(
                     resourceId,
                     new RefcountedResource<SpiRecord>(
-                            new SpiRecord(resourceId, direction, localAddress, remoteAddress, spi),
-                            binder));
+                            new SpiRecord(resourceId, "", destinationAddress, spi), binder));
         } catch (ServiceSpecificException e) {
             // TODO: Add appropriate checks when other ServiceSpecificException types are supported
             return new IpSecSpiResponse(
@@ -1032,27 +1016,27 @@
     }
 
     @VisibleForTesting
-    void validateAlgorithms(IpSecConfig config, int direction) throws IllegalArgumentException {
-            IpSecAlgorithm auth = config.getAuthentication(direction);
-            IpSecAlgorithm crypt = config.getEncryption(direction);
-            IpSecAlgorithm aead = config.getAuthenticatedEncryption(direction);
+    void validateAlgorithms(IpSecConfig config) throws IllegalArgumentException {
+        IpSecAlgorithm auth = config.getAuthentication();
+        IpSecAlgorithm crypt = config.getEncryption();
+        IpSecAlgorithm aead = config.getAuthenticatedEncryption();
 
-            // Validate the algorithm set
-            Preconditions.checkArgument(
-                    aead != null || crypt != null || auth != null,
-                    "No Encryption or Authentication algorithms specified");
-            Preconditions.checkArgument(
-                    auth == null || auth.isAuthentication(),
-                    "Unsupported algorithm for Authentication");
-            Preconditions.checkArgument(
+        // Validate the algorithm set
+        Preconditions.checkArgument(
+                aead != null || crypt != null || auth != null,
+                "No Encryption or Authentication algorithms specified");
+        Preconditions.checkArgument(
+                auth == null || auth.isAuthentication(),
+                "Unsupported algorithm for Authentication");
+        Preconditions.checkArgument(
                 crypt == null || crypt.isEncryption(), "Unsupported algorithm for Encryption");
-            Preconditions.checkArgument(
-                    aead == null || aead.isAead(),
-                    "Unsupported algorithm for Authenticated Encryption");
-            Preconditions.checkArgument(
-                    aead == null || (auth == null && crypt == null),
-                    "Authenticated Encryption is mutually exclusive with other Authentication "
-                                    + "or Encryption algorithms");
+        Preconditions.checkArgument(
+                aead == null || aead.isAead(),
+                "Unsupported algorithm for Authenticated Encryption");
+        Preconditions.checkArgument(
+                aead == null || (auth == null && crypt == null),
+                "Authenticated Encryption is mutually exclusive with other Authentication "
+                        + "or Encryption algorithms");
     }
 
     /**
@@ -1062,29 +1046,6 @@
     private void checkIpSecConfig(IpSecConfig config) {
         UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
 
-        if (config.getLocalAddress() == null) {
-            throw new IllegalArgumentException("Invalid null Local InetAddress");
-        }
-
-        if (config.getRemoteAddress() == null) {
-            throw new IllegalArgumentException("Invalid null Remote InetAddress");
-        }
-
-        switch (config.getMode()) {
-            case IpSecTransform.MODE_TRANSPORT:
-                if (!config.getLocalAddress().isEmpty()) {
-                    throw new IllegalArgumentException("Non-empty Local Address");
-                }
-                // Must be valid, and not a wildcard
-                checkInetAddress(config.getRemoteAddress());
-                break;
-            case IpSecTransform.MODE_TUNNEL:
-                break;
-            default:
-                throw new IllegalArgumentException(
-                        "Invalid IpSecTransform.mode: " + config.getMode());
-        }
-
         switch (config.getEncapType()) {
             case IpSecTransform.ENCAP_NONE:
                 break;
@@ -1103,11 +1064,36 @@
                 throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
         }
 
-        for (int direction : DIRECTIONS) {
-            validateAlgorithms(config, direction);
+        validateAlgorithms(config);
 
-            // Retrieve SPI record; will throw IllegalArgumentException if not found
-            userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId(direction));
+        // Retrieve SPI record; will throw IllegalArgumentException if not found
+        SpiRecord s = userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId());
+
+        // If no remote address is supplied, then use one from the SPI.
+        if (TextUtils.isEmpty(config.getDestinationAddress())) {
+            config.setDestinationAddress(s.getDestinationAddress());
+        }
+
+        // All remote addresses must match
+        if (!config.getDestinationAddress().equals(s.getDestinationAddress())) {
+            throw new IllegalArgumentException("Mismatched remote addresseses.");
+        }
+
+        // This check is technically redundant due to the chain of custody between the SPI and
+        // the IpSecConfig, but in the future if the dest is allowed to be set explicitly in
+        // the transform, this will prevent us from messing up.
+        checkInetAddress(config.getDestinationAddress());
+
+        // Require a valid source address for all transforms.
+        checkInetAddress(config.getSourceAddress());
+
+        switch (config.getMode()) {
+            case IpSecTransform.MODE_TRANSPORT:
+            case IpSecTransform.MODE_TUNNEL:
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Invalid IpSecTransform.mode: " + config.getMode());
         }
     }
 
@@ -1127,13 +1113,12 @@
 
         UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
 
-        // Avoid resizing by creating a dependency array of min-size 3 (1 UDP encap + 2 SPIs)
-        List<RefcountedResource> dependencies = new ArrayList<>(3);
+        // Avoid resizing by creating a dependency array of min-size 2 (1 UDP encap + 1 SPI)
+        List<RefcountedResource> dependencies = new ArrayList<>(2);
 
         if (!userRecord.mTransformQuotaTracker.isAvailable()) {
             return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
         }
-        SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
 
         int encapType, encapLocalPort = 0, encapRemotePort = 0;
         EncapSocketRecord socketRecord = null;
@@ -1149,51 +1134,46 @@
             encapRemotePort = c.getEncapRemotePort();
         }
 
-        for (int direction : DIRECTIONS) {
-            IpSecAlgorithm auth = c.getAuthentication(direction);
-            IpSecAlgorithm crypt = c.getEncryption(direction);
-            IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction);
+        IpSecAlgorithm auth = c.getAuthentication();
+        IpSecAlgorithm crypt = c.getEncryption();
+        IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption();
 
-            RefcountedResource<SpiRecord> refcountedSpiRecord =
-                    userRecord.mSpiRecords.getRefcountedResourceOrThrow(
-                            c.getSpiResourceId(direction));
-            dependencies.add(refcountedSpiRecord);
+        RefcountedResource<SpiRecord> refcountedSpiRecord =
+                userRecord.mSpiRecords.getRefcountedResourceOrThrow(c.getSpiResourceId());
+        dependencies.add(refcountedSpiRecord);
+        SpiRecord spiRecord = refcountedSpiRecord.getResource();
 
-            spis[direction] = refcountedSpiRecord.getResource();
-            int spi = spis[direction].getSpi();
-            try {
-                mSrvConfig
-                        .getNetdInstance()
-                        .ipSecAddSecurityAssociation(
-                                resourceId,
-                                c.getMode(),
-                                direction,
-                                c.getLocalAddress(),
-                                c.getRemoteAddress(),
-                                (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
-                                spi,
-                                (auth != null) ? auth.getName() : "",
-                                (auth != null) ? auth.getKey() : new byte[] {},
-                                (auth != null) ? auth.getTruncationLengthBits() : 0,
-                                (crypt != null) ? crypt.getName() : "",
-                                (crypt != null) ? crypt.getKey() : new byte[] {},
-                                (crypt != null) ? crypt.getTruncationLengthBits() : 0,
-                                (authCrypt != null) ? authCrypt.getName() : "",
-                                (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
-                                (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
-                                encapType,
-                                encapLocalPort,
-                                encapRemotePort);
-            } catch (ServiceSpecificException e) {
-                // FIXME: get the error code and throw is at an IOException from Errno Exception
-                return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
-            }
+        try {
+            mSrvConfig
+                    .getNetdInstance()
+                    .ipSecAddSecurityAssociation(
+                            resourceId,
+                            c.getMode(),
+                            c.getSourceAddress(),
+                            c.getDestinationAddress(),
+                            (c.getNetwork() != null) ? c.getNetwork().netId : 0,
+                            spiRecord.getSpi(),
+                            (auth != null) ? auth.getName() : "",
+                            (auth != null) ? auth.getKey() : new byte[] {},
+                            (auth != null) ? auth.getTruncationLengthBits() : 0,
+                            (crypt != null) ? crypt.getName() : "",
+                            (crypt != null) ? crypt.getKey() : new byte[] {},
+                            (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+                            (authCrypt != null) ? authCrypt.getName() : "",
+                            (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
+                            (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
+                            encapType,
+                            encapLocalPort,
+                            encapRemotePort);
+        } catch (ServiceSpecificException e) {
+            // FIXME: get the error code and throw is at an IOException from Errno Exception
+            return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
         }
         // Both SAs were created successfully, time to construct a record and lock it away
         userRecord.mTransformRecords.put(
                 resourceId,
                 new RefcountedResource<TransformRecord>(
-                        new TransformRecord(resourceId, c, spis, socketRecord),
+                        new TransformRecord(resourceId, c, spiRecord, socketRecord),
                         binder,
                         dependencies.toArray(new RefcountedResource[dependencies.size()])));
         return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
@@ -1217,9 +1197,9 @@
      */
     @Override
     public synchronized void applyTransportModeTransform(
-            ParcelFileDescriptor socket, int resourceId) throws RemoteException {
+            ParcelFileDescriptor socket, int direction, int resourceId) throws RemoteException {
         UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
-
+        checkDirection(direction);
         // Get transform record; if no transform is found, will throw IllegalArgumentException
         TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
 
@@ -1230,17 +1210,15 @@
 
         IpSecConfig c = info.getConfig();
         try {
-            for (int direction : DIRECTIONS) {
-                mSrvConfig
-                        .getNetdInstance()
-                        .ipSecApplyTransportModeTransform(
-                                socket.getFileDescriptor(),
-                                resourceId,
-                                direction,
-                                c.getLocalAddress(),
-                                c.getRemoteAddress(),
-                                info.getSpiRecord(direction).getSpi());
-            }
+            mSrvConfig
+                    .getNetdInstance()
+                    .ipSecApplyTransportModeTransform(
+                            socket.getFileDescriptor(),
+                            resourceId,
+                            direction,
+                            c.getSourceAddress(),
+                            c.getDestinationAddress(),
+                            info.getSpiRecord().getSpi());
         } catch (ServiceSpecificException e) {
             if (e.errorCode == EINVAL) {
                 throw new IllegalArgumentException(e.toString());
@@ -1251,14 +1229,14 @@
     }
 
     /**
-     * Remove a transport mode transform from a socket, applying the default (empty) policy. This
-     * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
-     * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
-     * used: reserved for future improved input validation.
+     * Remove transport mode transforms from a socket, applying the default (empty) policy. This
+     * ensures that NO IPsec policy is applied to the socket (would be the equivalent of applying a
+     * policy that performs no IPsec). Today the resourceId parameter is passed but not used:
+     * reserved for future improved input validation.
      */
     @Override
-    public synchronized void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
-            throws RemoteException {
+    public synchronized void removeTransportModeTransforms(
+            ParcelFileDescriptor socket, int resourceId) throws RemoteException {
         try {
             mSrvConfig
                     .getNetdInstance()
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/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..dfe89e0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -397,6 +397,7 @@
 import com.android.internal.os.TransferPipe;
 import com.android.internal.os.Zygote;
 import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.policy.KeyguardDismissCallback;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -6242,7 +6243,7 @@
 
                     // Clear its pending alarms
                     AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class);
-                    ami.removeAlarmsForUid(uid);
+                    ami.removeAlarmsForUid(appInfo.uid);
                 }
             } catch (RemoteException e) {
             }
@@ -7260,15 +7261,22 @@
             }
 
             ProfilerInfo profilerInfo = null;
-            String agent = null;
+            String preBindAgent = null;
             if (mProfileApp != null && mProfileApp.equals(processName)) {
                 mProfileProc = app;
-                profilerInfo = (mProfilerInfo != null && mProfilerInfo.profileFile != null) ?
-                        new ProfilerInfo(mProfilerInfo) : null;
-                agent = mProfilerInfo != null ? mProfilerInfo.agent : null;
+                if (mProfilerInfo != null) {
+                    // Send a profiler info object to the app if either a file is given, or
+                    // an agent should be loaded at bind-time.
+                    boolean needsInfo = mProfilerInfo.profileFile != null
+                            || mProfilerInfo.attachAgentDuringBind;
+                    profilerInfo = needsInfo ? new ProfilerInfo(mProfilerInfo) : null;
+                    if (!mProfilerInfo.attachAgentDuringBind) {
+                        preBindAgent = mProfilerInfo.agent;
+                    }
+                }
             } else if (app.instr != null && app.instr.mProfileFile != null) {
                 profilerInfo = new ProfilerInfo(app.instr.mProfileFile, null, 0, false, false,
-                        null);
+                        null, false);
             }
 
             boolean enableTrackAllocation = false;
@@ -7337,8 +7345,8 @@
 
             // If we were asked to attach an agent on startup, do so now, before we're binding
             // application code.
-            if (agent != null) {
-                thread.attachAgent(agent);
+            if (preBindAgent != null) {
+                thread.attachAgent(preBindAgent);
             }
 
             checkTime(startTime, "attachApplicationLocked: immediately before bindApplication");
@@ -8386,22 +8394,12 @@
                     // entering picture-in-picture (this will prompt the user to authenticate if the
                     // device is currently locked).
                     try {
-                        dismissKeyguard(token, new IKeyguardDismissCallback.Stub() {
-                            @Override
-                            public void onDismissError() throws RemoteException {
-                                // Do nothing
-                            }
-
+                        dismissKeyguard(token, new KeyguardDismissCallback() {
                             @Override
                             public void onDismissSucceeded() throws RemoteException {
                                 mHandler.post(enterPipRunnable);
                             }
-
-                            @Override
-                            public void onDismissCancelled() throws RemoteException {
-                                // Do nothing
-                            }
-                        });
+                        }, null /* message */);
                     } catch (RemoteException e) {
                         // Local call
                     }
@@ -25130,6 +25128,11 @@
         public void registerScreenObserver(ScreenObserver observer) {
             mScreenObservers.add(observer);
         }
+
+        @Override
+        public boolean canStartMoreUsers() {
+            return mUserController.canStartMoreUsers();
+        }
     }
 
     /**
@@ -25277,11 +25280,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 +25343,10 @@
                 }
             }
         }
+        if (updateFrameworkRes && mWindowManager != null) {
+            ActivityThread.currentActivityThread().getExecutor().execute(
+                    mWindowManager::onOverlayChanged);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 4f60e17..1240f5e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -109,6 +109,7 @@
     private boolean mAutoStop;
     private boolean mStreaming;   // Streaming the profiling output to a file.
     private String mAgent;  // Agent to attach on startup.
+    private boolean mAttachAgentDuringBind;  // Whether agent should be attached late.
     private int mDisplayId;
     private int mWindowingMode;
     private int mActivityType;
@@ -296,7 +297,21 @@
                 } else if (opt.equals("--streaming")) {
                     mStreaming = true;
                 } else if (opt.equals("--attach-agent")) {
+                    if (mAgent != null) {
+                        cmd.getErrPrintWriter().println(
+                                "Multiple --attach-agent(-bind) not supported");
+                        return false;
+                    }
                     mAgent = getNextArgRequired();
+                    mAttachAgentDuringBind = false;
+                } else if (opt.equals("--attach-agent-bind")) {
+                    if (mAgent != null) {
+                        cmd.getErrPrintWriter().println(
+                                "Multiple --attach-agent(-bind) not supported");
+                        return false;
+                    }
+                    mAgent = getNextArgRequired();
+                    mAttachAgentDuringBind = true;
                 } else if (opt.equals("-R")) {
                     mRepeat = Integer.parseInt(getNextArgRequired());
                 } else if (opt.equals("-S")) {
@@ -384,7 +399,7 @@
                     }
                 }
                 profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop,
-                        mStreaming, mAgent);
+                        mStreaming, mAgent, mAttachAgentDuringBind);
             }
 
             pw.println("Starting: " + intent);
@@ -766,7 +781,7 @@
                 return -1;
             }
             profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming,
-                    null);
+                    null, false);
         }
 
         try {
@@ -2544,6 +2559,7 @@
             pw.println("          (use with --start-profiler)");
             pw.println("      -P <FILE>: like above, but profiling stops when app goes idle");
             pw.println("      --attach-agent <agent>: attach the given agent before binding");
+            pw.println("      --attach-agent-bind <agent>: attach the given agent during binding");
             pw.println("      -R: repeat the activity launch <COUNT> times.  Prior to each repeat,");
             pw.println("          the top activity will be finished.");
             pw.println("      -S: force stop the target app before starting the activity");
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index b74c8da..ceec97c 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -21,6 +21,7 @@
 import static android.app.ActivityManager.TaskDescription.ATTR_TASKDESCRIPTION_PREFIX;
 import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
 import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
 import static android.app.ActivityOptions.ANIM_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
@@ -1481,6 +1482,10 @@
                 case ANIM_OPEN_CROSS_PROFILE_APPS:
                     service.mWindowManager.overridePendingAppTransitionStartCrossProfileApps();
                     break;
+                case ANIM_REMOTE_ANIMATION:
+                    service.mWindowManager.overridePendingAppTransitionRemote(
+                            pendingOptions.getRemoteAnimationAdapter());
+                    break;
                 default:
                     Slog.e(TAG, "applyOptionsLocked: Unknown animationType=" + animationType);
                     break;
@@ -1609,9 +1614,8 @@
             mStackSupervisor.mStoppingActivities.remove(this);
             mStackSupervisor.mGoingToSleepActivities.remove(this);
 
-            // If an activity is not in the paused state when becoming visible, cycle to the paused
-            // state.
-            if (state != PAUSED) {
+            // If the activity is stopped or stopping, cycle to the paused state.
+            if (state == STOPPED || state == STOPPING) {
                 // An activity must be in the {@link PAUSING} state for the system to validate
                 // the move to {@link PAUSED}.
                 state = PAUSING;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index b567303..16c2d2d 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import static android.Manifest.permission.ACTIVITY_EMBEDDING;
+import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.START_ANY_ACTIVITY;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
@@ -162,6 +163,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
+import android.view.RemoteAnimationAdapter;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.ReferrerIntent;
@@ -1680,6 +1682,18 @@
                 Slog.w(TAG, msg);
                 throw new SecurityException(msg);
             }
+
+            // Check permission for remote animations
+            final RemoteAnimationAdapter adapter = options.getRemoteAnimationAdapter();
+            if (adapter != null && mService.checkPermission(
+                    CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, callingPid, callingUid)
+                            != PERMISSION_GRANTED) {
+                final String msg = "Permission Denial: starting " + intent.toString()
+                        + " from " + callerApp + " (pid=" + callingPid
+                        + ", uid=" + callingUid + ") with remoteAnimationAdapter";
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
         }
 
         return true;
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/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index d957ca0..51499f7 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,32 @@
             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();
+            final long runtime = nowUptime - startUptime;
+
+            if (startUptime == 0) {
+                wtf("Job " + jobId + " start uptime not found: "
+                        + " params=" + jobParametersToString(params));
+            } else if (runtime > 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));
+                }
+            } else if (runtime < 10 * 1000) {
+                // Job stopped too soon. WTF.
+                wtf("Job " + jobId + " stopped in " + runtime + " ms: "
+                        + " startUptime=" + startUptime
+                        + " nowUptime=" + nowUptime
+                        + " params=" + jobParametersToString(params));
             }
 
             mStartedSyncs.delete(jobId);
+            mJobStartUptimes.delete(jobId);
         }
         Message m = Message.obtain();
         m.what = SyncManager.SyncHandler.MESSAGE_STOP_SYNC;
@@ -166,7 +191,13 @@
             return "job:null";
         } else {
             return "job:#" + params.getJobId() + ":"
+                    + "sr=[" + params.getStopReason() + "/" + params.getDebugStopReason() + "]:"
                     + 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/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 02e4fe0..a55fec5 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2009,5 +2009,14 @@
                 mDisplayPowerController.persistBrightnessSliderEvents();
             }
         }
+
+        @Override
+        public void onOverlayChanged() {
+            synchronized (mSyncRoot) {
+                if (updateLogicalDisplaysLocked()) {
+                    scheduleTraversalLocked(false);
+                }
+            }
+        }
     }
 }
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/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 733ed3d..bd1dbf9 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -934,12 +934,14 @@
      * @param uid Uid of the calling client.
      * @param jobId Id of the job, provided at schedule-time.
      */
-    public boolean cancelJob(int uid, int jobId) {
+    public boolean cancelJob(int uid, int jobId, int callingUid) {
         JobStatus toCancel;
         synchronized (mLock) {
             toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
             if (toCancel != null) {
-                cancelJobImplLocked(toCancel, null, "cancel() called by app");
+                cancelJobImplLocked(toCancel, null,
+                        "cancel() called by app, callingUid=" + callingUid
+                        + " uid=" + uid + " jobId=" + jobId);
             }
             return (toCancel != null);
         }
@@ -2341,7 +2343,8 @@
             final int uid = Binder.getCallingUid();
             long ident = Binder.clearCallingIdentity();
             try {
-                JobSchedulerService.this.cancelJobsForUid(uid, "cancelAll() called by app");
+                JobSchedulerService.this.cancelJobsForUid(uid,
+                        "cancelAll() called by app, callingUid=" + uid);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2353,7 +2356,7 @@
 
             long ident = Binder.clearCallingIdentity();
             try {
-                JobSchedulerService.this.cancelJob(uid, jobId);
+                JobSchedulerService.this.cancelJob(uid, jobId, uid);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2466,7 +2469,7 @@
             for (int i=0; i<mActiveServices.size(); i++) {
                 final JobServiceContext jc = mActiveServices.get(i);
                 final JobStatus js = jc.getRunningJobLocked();
-                if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId)) {
+                if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) {
                     foundSome = true;
                     pw.print("Timing out: ");
                     js.printUniqueId(pw);
@@ -2506,7 +2509,7 @@
             }
         } else {
             pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
-            if (!cancelJob(pkgUid, jobId)) {
+            if (!cancelJob(pkgUid, jobId, Process.SHELL_UID)) {
                 pw.println("No matching job found.");
             }
         }
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 709deeb..83a3c19 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -312,13 +312,14 @@
         return mTimeoutElapsed;
     }
 
-    boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId) {
+    boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId,
+            String reason) {
         final JobStatus executing = getRunningJobLocked();
         if (executing != null && (userId == UserHandle.USER_ALL || userId == executing.getUserId())
                 && (pkgName == null || pkgName.equals(executing.getSourcePackageName()))
                 && (!matchJobId || jobId == executing.getJobId())) {
             if (mVerb == VERB_EXECUTING) {
-                mParams.setStopReason(JobParameters.REASON_TIMEOUT);
+                mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
                 sendStopMessageLocked("force timeout from shell");
                 return true;
             }
@@ -537,7 +538,7 @@
             }
             return;
         }
-        mParams.setStopReason(arg1);
+        mParams.setStopReason(arg1, debugReason);
         if (arg1 == JobParameters.REASON_PREEMPT) {
             mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
                     NO_PREFERRED_UID;
@@ -687,7 +688,7 @@
                 // Not an error - client ran out of time.
                 Slog.i(TAG, "Client timed out while executing (no jobFinished received), " +
                         "sending onStop: " + getRunningJobNameLocked());
-                mParams.setStopReason(JobParameters.REASON_TIMEOUT);
+                mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out");
                 sendStopMessageLocked("timeout while executing");
                 break;
             default:
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 5a8005c..db94028 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -82,7 +82,7 @@
 import android.security.keystore.EntryRecoveryData;
 import android.security.keystore.RecoveryData;
 import android.security.keystore.RecoveryMetadata;
-import android.security.keystore.RecoveryManager.RecoveryManagerException;
+import android.security.keystore.RecoveryManagerException;
 import android.service.gatekeeper.GateKeeperResponse;
 import android.service.gatekeeper.IGateKeeperService;
 import android.text.TextUtils;
@@ -90,6 +90,7 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -1874,6 +1875,7 @@
         mSpManager.removeUser(userId);
         mStorage.removeUser(userId);
         mStrongAuth.removeUser(userId);
+        cleanSpCache();
 
         final KeyStore ks = KeyStore.getInstance();
         ks.onUserRemoved(userId);
@@ -1982,8 +1984,8 @@
     }
 
     @Override
-    public void setServerParameters(long serverParameters) throws RemoteException {
-        mRecoverableKeyStoreManager.setServerParameters(serverParameters);
+    public void setServerParams(byte[] serverParams) throws RemoteException {
+        mRecoverableKeyStoreManager.setServerParams(serverParams);
     }
 
     @Override
@@ -2112,6 +2114,63 @@
     }
 
     /**
+     * A user's synthetic password does not change so it must be cached in certain circumstances to
+     * enable untrusted credential reset.
+     *
+     * Untrusted credential reset will be removed in a future version (b/68036371) at which point
+     * this cache is no longer needed as the SP will always be known when changing the user's
+     * credential.
+     */
+    @GuardedBy("mSpManager")
+    private SparseArray<AuthenticationToken> mSpCache = new SparseArray();
+
+    private void onAuthTokenKnownForUser(@UserIdInt int userId, AuthenticationToken auth) {
+        // Update the SP cache, removing the entry when allowed
+        synchronized (mSpManager) {
+            if (shouldCacheSpForUser(userId)) {
+                Slog.i(TAG, "Caching SP for user " + userId);
+                mSpCache.put(userId, auth);
+            } else {
+                Slog.i(TAG, "Not caching SP for user " + userId);
+                mSpCache.delete(userId);
+            }
+        }
+    }
+
+    /** Clean up the SP cache by removing unneeded entries. */
+    private void cleanSpCache() {
+        synchronized (mSpManager) {
+            // Preserve indicies after removal by iterating backwards
+            for (int i = mSpCache.size() - 1; i >= 0; --i) {
+                final int userId = mSpCache.keyAt(i);
+                if (!shouldCacheSpForUser(userId)) {
+                    Slog.i(TAG, "Uncaching SP for user " + userId);
+                    mSpCache.removeAt(i);
+                }
+            }
+        }
+    }
+
+    private boolean shouldCacheSpForUser(@UserIdInt int userId) {
+        // Before the user setup has completed, an admin could be installed that requires the SP to
+        // be cached (see below).
+        if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.USER_SETUP_COMPLETE, 0, userId) == 0) {
+            return true;
+        }
+
+        // If the user has an admin which can perform an untrusted credential reset, the SP needs to
+        // be cached. If there isn't a DevicePolicyManager then there can't be an admin in the first
+        // place so caching is not necessary.
+        final DevicePolicyManagerInternal dpmi = LocalServices.getService(
+                DevicePolicyManagerInternal.class);
+        if (dpmi == null) {
+            return false;
+        }
+        return dpmi.canUserHaveUntrustedCredentialReset(userId);
+    }
+
+    /**
      * Precondition: vold and keystore unlocked.
      *
      * Create new synthetic password, set up synthetic password blob protected by the supplied
@@ -2126,9 +2185,7 @@
      * 3. Once a user is migrated to have synthetic password, its value will never change, no matter
      *     whether the user changes his lockscreen PIN or clear/reset it. When the user clears its
      *     lockscreen PIN, we still maintain the existing synthetic password in a password blob
-     *     protected by a default PIN. The only exception is when the DPC performs an untrusted
-     *     credential change, in which case we have no way to derive the existing synthetic password
-     *     and has to create a new one.
+     *     protected by a default PIN.
      * 4. The user SID is linked with synthetic password, but its cleared/re-created when the user
      *     clears/re-creates his lockscreen PIN.
      *
@@ -2148,13 +2205,23 @@
      *     This is the untrusted credential reset, OR the user sets a new lockscreen password
      *     FOR THE FIRST TIME on a SP-enabled device. New credential and new SID will be created
      */
+    @GuardedBy("mSpManager")
     @VisibleForTesting
     protected AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash,
             String credential, int credentialType, int requestedQuality,
             int userId) throws RemoteException {
         Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
-        AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(getGateKeeperService(),
-                credentialHash, credential, userId);
+        // Load from the cache or a make a new one
+        AuthenticationToken auth = mSpCache.get(userId);
+        if (auth != null) {
+            // If the synthetic password has been cached, we can only be in case 3., described
+            // above, for an untrusted credential reset so a new SID is still needed.
+            mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
+        } else {
+            auth = mSpManager.newSyntheticPasswordAndSid(getGateKeeperService(),
+                      credentialHash, credential, userId);
+        }
+        onAuthTokenKnownForUser(userId, auth);
         if (auth == null) {
             Slog.wtf(TAG, "initializeSyntheticPasswordLocked returns null auth token");
             return null;
@@ -2269,6 +2336,8 @@
                 trustManager.setDeviceLockedForUser(userId, false);
             }
             mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
+
+            onAuthTokenKnownForUser(userId, authResult.authToken);
         } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
             if (response.getTimeout() > 0) {
                 requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
@@ -2287,6 +2356,7 @@
      * SID is gone. We also clear password from (software-based) keystore and vold, which will be
      * added back when new password is set in future.
      */
+    @GuardedBy("mSpManager")
     private long setLockCredentialWithAuthTokenLocked(String credential, int credentialType,
             AuthenticationToken auth, int requestedQuality, int userId) throws RemoteException {
         if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId);
@@ -2334,6 +2404,7 @@
         return newHandle;
     }
 
+    @GuardedBy("mSpManager")
     private void spBasedSetLockCredentialInternalLocked(String credential, int credentialType,
             String savedCredential, int requestedQuality, int userId) throws RemoteException {
         if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId);
@@ -2369,13 +2440,19 @@
             setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, requestedQuality,
                     userId);
             mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
+            onAuthTokenKnownForUser(userId, auth);
         } else if (response != null
-                && response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR){
+                && response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR) {
             // We are performing an untrusted credential change i.e. by DevicePolicyManager.
             // So provision a new SP and SID. This would invalidate existing escrow tokens.
             // Still support this for now but this flow will be removed in the next release.
-
             Slog.w(TAG, "Untrusted credential change invoked");
+
+            if (mSpCache.get(userId) == null) {
+                throw new IllegalStateException(
+                        "Untrusted credential reset not possible without cached SP");
+            }
+
             initializeSyntheticPasswordLocked(null, credential, credentialType, requestedQuality,
                     userId);
             synchronizeUnifiedWorkChallengeForProfiles(userId, null);
@@ -2486,8 +2563,9 @@
 
     private boolean setLockCredentialWithTokenInternal(String credential, int type,
             long tokenHandle, byte[] token, int requestedQuality, int userId) throws RemoteException {
+        final AuthenticationResult result;
         synchronized (mSpManager) {
-            AuthenticationResult result = mSpManager.unwrapTokenBasedSyntheticPassword(
+            result = mSpManager.unwrapTokenBasedSyntheticPassword(
                     getGateKeeperService(), tokenHandle, token, userId);
             if (result.authToken == null) {
                 Slog.w(TAG, "Invalid escrow token supplied");
@@ -2508,8 +2586,9 @@
             setLockCredentialWithAuthTokenLocked(credential, type, result.authToken,
                     requestedQuality, userId);
             mSpManager.destroyPasswordBasedSyntheticPassword(oldHandle, userId);
-            return true;
         }
+        onAuthTokenKnownForUser(userId, result.authToken);
+        return true;
     }
 
     @Override
@@ -2529,6 +2608,7 @@
             }
         }
         unlockUser(userId, null, authResult.authToken.deriveDiskEncryptionKey());
+        onAuthTokenKnownForUser(userId, authResult.authToken);
     }
 
     @Override
@@ -2610,6 +2690,8 @@
     private class DeviceProvisionedObserver extends ContentObserver {
         private final Uri mDeviceProvisionedUri = Settings.Global.getUriFor(
                 Settings.Global.DEVICE_PROVISIONED);
+        private final Uri mUserSetupCompleteUri = Settings.Secure.getUriFor(
+                Settings.Secure.USER_SETUP_COMPLETE);
 
         private boolean mRegistered;
 
@@ -2627,6 +2709,8 @@
                     reportDeviceSetupComplete();
                     clearFrpCredentialIfOwnerNotSecure();
                 }
+            } else if (mUserSetupCompleteUri.equals(uri)) {
+                cleanSpCache();
             }
         }
 
@@ -2678,6 +2762,8 @@
             if (register) {
                 mContext.getContentResolver().registerContentObserver(mDeviceProvisionedUri,
                         false, this);
+                mContext.getContentResolver().registerContentObserver(mUserSetupCompleteUri,
+                        false, this, UserHandle.USER_ALL);
             } else {
                 mContext.getContentResolver().unregisterContentObserver(this);
             }
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 57d0041..5fe11b1 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -21,7 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.security.keystore.KeyDerivationParameters;
+import android.security.keystore.KeyDerivationParams;
 import android.security.keystore.EntryRecoveryData;
 import android.security.keystore.RecoveryData;
 import android.security.keystore.RecoveryMetadata;
@@ -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 {
@@ -254,7 +254,7 @@
         RecoveryMetadata metadata = new RecoveryMetadata(
                 /*userSecretType=*/ TYPE_LOCKSCREEN,
                 /*lockScreenUiFormat=*/ getUiFormat(mCredentialType, mCredential),
-                /*keyDerivationParameters=*/ KeyDerivationParameters.createSha256Parameters(salt),
+                /*keyDerivationParams=*/ KeyDerivationParams.createSha256Params(salt),
                 /*secret=*/ new byte[0]);
         ArrayList<RecoveryMetadata> metadataList = new ArrayList<>();
         metadataList.add(metadata);
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 0ab8dc5..b4bef17 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -287,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 0e93f94..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,13 +16,13 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
-import static android.security.keystore.RecoveryManager.ERROR_BAD_X509_CERTIFICATE;
-import static android.security.keystore.RecoveryManager.ERROR_DATABASE_ERROR;
-import static android.security.keystore.RecoveryManager.ERROR_DECRYPTION_FAILED;
-import static android.security.keystore.RecoveryManager.ERROR_INSECURE_USER;
-import static android.security.keystore.RecoveryManager.ERROR_KEYSTORE_INTERNAL_ERROR;
-import static android.security.keystore.RecoveryManager.ERROR_NOT_YET_SUPPORTED;
-import static android.security.keystore.RecoveryManager.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;
@@ -177,7 +177,7 @@
         int uid = Binder.getCallingUid();
         RecoveryData snapshot = mSnapshotStorage.get(uid);
         if (snapshot == null) {
-            throw new ServiceSpecificException(RecoveryManager.ERROR_NO_SNAPSHOT_PENDING);
+            throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING);
         }
         return snapshot;
     }
@@ -201,11 +201,11 @@
         throw new UnsupportedOperationException();
     }
 
-    public void setServerParameters(long serverParameters) throws RemoteException {
+    public void setServerParams(byte[] serverParams) throws RemoteException {
         checkRecoverKeyStorePermission();
         int userId = UserHandle.getCallingUserId();
         int uid = Binder.getCallingUid();
-        long updatedRows = mDatabase.setServerParameters(userId, uid, serverParameters);
+        long updatedRows = mDatabase.setServerParams(userId, uid, serverParams);
         if (updatedRows > 0) {
             mDatabase.setShouldCreateSnapshot(userId, uid, true);
         }
@@ -326,10 +326,7 @@
         int uid = Binder.getCallingUid();
 
         if (secrets.size() != 1) {
-            // TODO: support multiple secrets
-            throw new ServiceSpecificException(
-                    ERROR_NOT_YET_SUPPORTED,
-                    "Only a single RecoveryMetadata is supported");
+            throw new UnsupportedOperationException("Only a single RecoveryMetadata is supported");
         }
 
         PublicKey publicKey;
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 8674330..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,56 +384,20 @@
      */
     @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;
         }
     }
 
@@ -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 a6d9bd4..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
@@ -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/media/MediaUpdateService.java b/services/core/java/com/android/server/media/MediaUpdateService.java
new file mode 100644
index 0000000..016d062
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaUpdateService.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 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.media;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.IMediaResourceMonitor;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+import android.util.Slog;
+import com.android.server.SystemService;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.PatternMatcher;
+import android.os.ServiceManager;
+import android.media.IMediaExtractorUpdateService;
+
+import java.lang.Exception;
+
+/** This class provides a system service that manages media framework updates. */
+public class MediaUpdateService extends SystemService {
+    private static final String TAG = "MediaUpdateService";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final String MEDIA_UPDATE_PACKAGE_NAME = "com.android.media.update";
+    private static final String EXTRACTOR_UPDATE_SERVICE_NAME = "media.extractor.update";
+
+    private IMediaExtractorUpdateService mMediaExtractorUpdateService;
+
+    public MediaUpdateService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        // TODO: Uncomment below once sepolicy change is landed.
+        /*
+        if ("userdebug".equals(android.os.Build.TYPE) || "eng".equals(android.os.Build.TYPE)) {
+            connect();
+            registerBroadcastReceiver();
+        }
+        */
+    }
+
+    private void connect() {
+        IBinder binder = ServiceManager.getService(EXTRACTOR_UPDATE_SERVICE_NAME);
+        if (binder != null) {
+            try {
+                binder.linkToDeath(new IBinder.DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        Slog.w(TAG, "mediaextractor died; reconnecting");
+                        mMediaExtractorUpdateService = null;
+                        connect();
+                    }
+                }, 0);
+            } catch (Exception e) {
+                binder = null;
+            }
+        }
+        if (binder != null) {
+            mMediaExtractorUpdateService = IMediaExtractorUpdateService.Stub.asInterface(binder);
+            packageStateChanged();
+        } else {
+            Slog.w(TAG, EXTRACTOR_UPDATE_SERVICE_NAME + " not found.");
+        }
+    }
+
+    private void registerBroadcastReceiver() {
+        BroadcastReceiver updateReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM)
+                            != UserHandle.USER_SYSTEM) {
+                        // Ignore broadcast for non system users. We don't want to update system
+                        // service multiple times.
+                        return;
+                    }
+                    switch (intent.getAction()) {
+                        case Intent.ACTION_PACKAGE_REMOVED:
+                            if (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) {
+                                // The existing package is updated. Will be handled with the
+                                // following ACTION_PACKAGE_ADDED case.
+                                return;
+                            }
+                            packageStateChanged();
+                            break;
+                        case Intent.ACTION_PACKAGE_CHANGED:
+                            packageStateChanged();
+                            break;
+                        case Intent.ACTION_PACKAGE_ADDED:
+                            packageStateChanged();
+                            break;
+                    }
+                }
+        };
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addDataScheme("package");
+        filter.addDataSchemeSpecificPart(MEDIA_UPDATE_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
+
+        getContext().registerReceiverAsUser(updateReceiver, UserHandle.ALL, filter,
+                null /* broadcast permission */, null /* handler */);
+    }
+
+    private void packageStateChanged() {
+        ApplicationInfo packageInfo = null;
+        boolean pluginsAvailable = false;
+        try {
+            packageInfo = getContext().getPackageManager().getApplicationInfo(
+                    MEDIA_UPDATE_PACKAGE_NAME, PackageManager.MATCH_SYSTEM_ONLY);
+            pluginsAvailable = packageInfo.enabled;
+        } catch (Exception e) {
+            Slog.v(TAG, "package '" + MEDIA_UPDATE_PACKAGE_NAME + "' not installed");
+        }
+        loadExtractorPlugins(
+                (packageInfo != null && pluginsAvailable) ? packageInfo.sourceDir : "");
+    }
+
+    private void loadExtractorPlugins(String apkPath) {
+        try {
+            if (mMediaExtractorUpdateService != null) {
+                mMediaExtractorUpdateService.loadPlugins(apkPath);
+            }
+        } catch (Exception e) {
+            Slog.w(TAG, "Error in loadPlugins", e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 37e6ae9..42093e8 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -71,6 +71,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -98,6 +99,9 @@
     static final String ATT_APPROVED_LIST = "approved";
     static final String ATT_USER_ID = "user";
     static final String ATT_IS_PRIMARY = "primary";
+    static final String ATT_VERSION = "version";
+
+    static final int DB_VERSION = 1;
 
     static final int APPROVAL_BY_PACKAGE = 0;
     static final int APPROVAL_BY_COMPONENT = 1;
@@ -295,6 +299,8 @@
     public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
         out.startTag(null, getConfig().xmlTag);
 
+        out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION));
+
         if (forBackup) {
             trimApprovedListsAccordingToInstalledServices();
         }
@@ -336,6 +342,14 @@
 
     public void readXml(XmlPullParser parser)
             throws XmlPullParserException, IOException {
+        // upgrade xml
+        int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0);
+        final List<UserInfo> activeUsers = mUm.getUsers(true);
+        for (UserInfo userInfo : activeUsers) {
+            upgradeXml(xmlVersion, userInfo.getUserHandle().getIdentifier());
+        }
+
+        // read grants
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
             String tag = parser.getName();
@@ -346,6 +360,7 @@
             if (type == XmlPullParser.START_TAG) {
                 if (TAG_MANAGED_SERVICES.equals(tag)) {
                     Slog.i(TAG, "Read " + mConfig.caption + " permissions from xml");
+
                     final String approved = XmlUtils.readStringAttribute(parser, ATT_APPROVED_LIST);
                     final int userId = XmlUtils.readIntAttribute(parser, ATT_USER_ID, 0);
                     final boolean isPrimary =
@@ -360,6 +375,8 @@
         rebindServices(false);
     }
 
+    protected void upgradeXml(final int xmlVersion, final int userId) {}
+
     private void loadAllowedComponentsFromSettings() {
 
         UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
@@ -379,7 +396,7 @@
         Slog.d(TAG, "Done loading approved values from settings");
     }
 
-    private void addApprovedList(String approved, int userId, boolean isPrimary) {
+    protected void addApprovedList(String approved, int userId, boolean isPrimary) {
         if (TextUtils.isEmpty(approved)) {
             approved = "";
         }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 575c44d..2f6618f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -434,6 +434,7 @@
                 }
             }
         }
+
         String defaultDndAccess = getContext().getResources().getString(
                 com.android.internal.R.string.config_defaultDndAccessPackages);
         if (defaultListenerAccess != null) {
@@ -446,6 +447,29 @@
                 }
             }
         }
+
+        readDefaultAssistant(userId);
+    }
+
+    protected void readDefaultAssistant(int userId) {
+        String defaultAssistantAccess = getContext().getResources().getString(
+                com.android.internal.R.string.config_defaultAssistantAccessPackage);
+        if (defaultAssistantAccess != null) {
+            // Gather all notification assistant components for candidate pkg. There should
+            // only be one
+            Set<ComponentName> approvedAssistants =
+                    mAssistants.queryPackageForServices(defaultAssistantAccess,
+                            PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+            for (ComponentName cn : approvedAssistants) {
+                try {
+                    getBinderService().setNotificationAssistantAccessGrantedForUser(cn,
+                            userId, true);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
     }
 
     void readPolicyXml(InputStream stream, boolean forRestore)
@@ -1155,6 +1179,7 @@
         }
     }
 
+    @VisibleForTesting
     void clearNotifications() {
         mEnqueuedNotifications.clear();
         mNotificationList.clear();
@@ -1374,7 +1399,8 @@
                 AppGlobals.getPackageManager(), getContext().getPackageManager(),
                 getLocalService(LightsManager.class),
                 new NotificationListeners(AppGlobals.getPackageManager()),
-                new NotificationAssistants(AppGlobals.getPackageManager()),
+                new NotificationAssistants(getContext(), mNotificationLock, mUserProfiles,
+                        AppGlobals.getPackageManager()),
                 new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()),
                 null, snoozeHelper, new NotificationUsageStats(getContext()),
                 new AtomicFile(new File(systemDir, "notification_policy.xml")),
@@ -2917,12 +2943,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);
             }
@@ -5531,8 +5579,9 @@
     public class NotificationAssistants extends ManagedServices {
         static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";
 
-        public NotificationAssistants(IPackageManager pm) {
-            super(getContext(), mNotificationLock, mUserProfiles, pm);
+        public NotificationAssistants(Context context, Object lock, UserProfiles up,
+                IPackageManager pm) {
+            super(context, lock, up, pm);
         }
 
         @Override
@@ -5637,6 +5686,14 @@
         public boolean isEnabled() {
             return !getServices().isEmpty();
         }
+
+        protected void upgradeXml(final int xmlVersion, final int userId) {
+            if (xmlVersion == 0) {
+                // one time approval of the OOB assistant
+                Slog.d(TAG, "Approving default notification assistant for user " + userId);
+                readDefaultAssistant(userId);
+            }
+        }
     }
 
     public class NotificationListeners extends ManagedServices {
@@ -6050,7 +6107,7 @@
         public static final String USAGE = "help\n"
                 + "allow_listener COMPONENT [user_id]\n"
                 + "disallow_listener COMPONENT [user_id]\n"
-                + "set_assistant COMPONENT\n"
+                + "allow_assistant COMPONENT\n"
                 + "remove_assistant COMPONENT\n"
                 + "allow_dnd PACKAGE\n"
                 + "disallow_dnd PACKAGE";
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/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index be66fe2..4b3758d 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -281,13 +281,14 @@
     public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
             int dexoptNeeded, @Nullable String outputPath, int dexFlags,
             String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries,
-            @Nullable String seInfo, boolean downgrade)
+            @Nullable String seInfo, boolean downgrade, int targetSdkVersion)
             throws InstallerException {
         assertValidInstructionSet(instructionSet);
         if (!checkBeforeRemote()) return;
         try {
             mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
-                    dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade);
+                    dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade,
+                    targetSdkVersion);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index b06b583..1717b3d 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -652,6 +652,7 @@
                             activityInfo.name.equals(component.getClassName())) {
                         // Found an activity with category launcher that matches
                         // this component so ok to launch.
+                        launchIntent.setPackage(null);
                         launchIntent.setComponent(component);
                         mContext.startActivityAsUser(launchIntent, opts, user);
                         return;
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 03f662a..0395011 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -260,12 +260,13 @@
             public void dexopt(String apkPath, int uid, @Nullable String pkgName,
                     String instructionSet, int dexoptNeeded, @Nullable String outputPath,
                     int dexFlags, String compilerFilter, @Nullable String volumeUuid,
-                    @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade)
+                    @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade,
+                    int targetSdkVersion)
                     throws InstallerException {
                 final StringBuilder builder = new StringBuilder();
 
-                // The version. Right now it's 3.
-                builder.append("3 ");
+                // The version. Right now it's 4.
+                builder.append("4 ");
 
                 builder.append("dexopt");
 
@@ -281,6 +282,7 @@
                 encodeParameter(builder, sharedLibraries);
                 encodeParameter(builder, seInfo);
                 encodeParameter(builder, downgrade);
+                encodeParameter(builder, targetSdkVersion);
 
                 commands.add(builder.toString());
             }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 730a9fd..91df87b 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -274,7 +274,7 @@
             // primary dex files.
             mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
                     compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo,
-                    false /* downgrade*/);
+                    false /* downgrade*/, pkg.applicationInfo.targetSdkVersion);
 
             if (packageStats != null) {
                 long endTime = System.currentTimeMillis();
@@ -395,7 +395,7 @@
                 mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
                         /*oatDir*/ null, dexoptFlags,
                         compilerFilter, info.volumeUuid, classLoaderContext, info.seInfoUser,
-                        options.isDowngrade());
+                        options.isDowngrade(), info.targetSdkVersion);
             }
 
             return DEX_OPT_PERFORMED;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f7f78b9..6e94523 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8193,12 +8193,12 @@
     }
 
     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)
@@ -8221,7 +8221,8 @@
             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 {
@@ -8530,8 +8531,11 @@
                     + " better than this " + pkg.getLongVersionCode());
         }
 
-        // verify certificates against what was last scanned
-        collectCertificatesLI(pkgSetting, pkg, parseFlags);
+        // 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));
 
         boolean shouldHideSystemApp = false;
         // A new application appeared on /system, but, we already have a copy of
@@ -9712,58 +9716,58 @@
             this.originalPkgSetting = originalPkgSetting;
             this.realPkgName = realPkgName;
             this.parseFlags = parseFlags;
-            this.scanFlags = adjustScanFlags(scanFlags, pkgSetting, disabledPkgSetting, user);
+            this.scanFlags = scanFlags;
             this.isPlatformPackage = isPlatformPackage;
             this.user = user;
         }
+    }
 
-        /**
-         * 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;
-                }
+    /**
+     * 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 (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;
-                }
+            if ((disabledPkgSetting.pkgPrivateFlags
+                    & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
+                scanFlags |= SCAN_AS_OEM;
             }
-            return scanFlags;
+            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);
@@ -9781,6 +9785,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);
@@ -9835,6 +9840,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;
@@ -9917,7 +9923,7 @@
             try {
                 final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
                 final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
-                final boolean compatMatch = verifySignatures(signatureCheckPs,
+                final boolean compatMatch = verifySignatures(signatureCheckPs, disabledPkgSetting,
                         pkg.mSigningDetails, compareCompat, compareRecover);
                 // The new KeySets will be re-added later in the scanning process.
                 if (compatMatch) {
@@ -16687,8 +16693,9 @@
                     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.mSigningDetails, compareCompat,
+                                signatureCheckPs, null, pkg.mSigningDetails, compareCompat,
                                 compareRecover);
                         // The new KeySets will be re-added later in the scanning process.
                         if (compatMatch) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index df836de..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;
@@ -547,13 +550,46 @@
     }
 
     /**
+     * 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,
-            PackageParser.SigningDetails parsedSignatures, boolean compareCompat,
-            boolean compareRecover)
+            PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
+            boolean compareCompat, boolean compareRecover)
             throws PackageManagerException {
         final String packageName = pkgSetting.name;
         boolean compatMatch = false;
@@ -572,6 +608,11 @@
                         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 +
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 0d7ef66..2a2430c 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -122,7 +122,7 @@
                 + ", oABI: " + cpuAbiOverrideString
                 + ", flags: " + flags
                 + ", privFlags: " + privFlags
-                + ", pkg: " + (pkg == null ? "<<NULL>>" : "{" + Integer.toHexString(System.identityHashCode(pkg)) + "}")
+                + ", pkg: " + (pkg == null ? "<<NULL>>" : pkg.dumpState_temp())
                 + "}";
     }
 
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 b6a094c..cc07d82 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -25,12 +25,14 @@
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
+import android.provider.Settings;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.util.Log;
@@ -117,7 +119,10 @@
             UserManager.DISALLOW_UNIFIED_PASSWORD,
             UserManager.DISALLOW_CONFIG_LOCATION_MODE,
             UserManager.DISALLOW_AIRPLANE_MODE,
-            UserManager.DISALLOW_CONFIG_BRIGHTNESS
+            UserManager.DISALLOW_CONFIG_BRIGHTNESS,
+            UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
+            UserManager.DISALLOW_AMBIENT_DISPLAY,
+            UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT
     });
 
     /**
@@ -540,6 +545,22 @@
                             android.provider.Settings.Global.SAFE_BOOT_DISALLOWED,
                             newValue ? 1 : 0);
                     break;
+                case UserManager.DISALLOW_AIRPLANE_MODE:
+                    if (newValue) {
+                        final boolean airplaneMode = Settings.Global.getInt(
+                                context.getContentResolver(),
+                                Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+                        if (airplaneMode) {
+                            android.provider.Settings.Global.putInt(
+                                    context.getContentResolver(),
+                                    android.provider.Settings.Global.AIRPLANE_MODE_ON, 0);
+                            // Post the intent.
+                            Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+                            intent.putExtra("state", 0);
+                            context.sendBroadcastAsUser(intent, UserHandle.ALL);
+                        }
+                    }
+                    break;
             }
         } finally {
             Binder.restoreCallingIdentity(id);
diff --git a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
index d6281c5..a517d6d 100644
--- a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
@@ -111,6 +111,7 @@
 
         final long ident = mInjector.clearCallingIdentity();
         try {
+            launchIntent.setPackage(null);
             launchIntent.setComponent(component);
             mContext.startActivityAsUser(launchIntent,
                     ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(), user);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a1852fe..a453c33 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -48,7 +48,6 @@
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 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_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
@@ -65,6 +64,9 @@
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
@@ -161,6 +163,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;
@@ -266,9 +269,11 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
+import com.android.internal.policy.KeyguardDismissCallback;
 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;
@@ -460,6 +465,7 @@
     AccessibilityManager mAccessibilityManager;
     BurnInProtectionHelper mBurnInProtectionHelper;
     AppOpsManager mAppOpsManager;
+    private ScreenshotHelper mScreenshotHelper;
     private boolean mHasFeatureWatch;
     private boolean mHasFeatureLeanback;
 
@@ -621,8 +627,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.
@@ -981,9 +985,6 @@
             resolver.registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.POLICY_CONTROL), false, this,
                     UserHandle.USER_ALL);
-            resolver.registerContentObserver(Settings.Global.getUriFor(
-                    Settings.Global.EMULATE_DISPLAY_CUTOUT), false, this,
-                    UserHandle.USER_ALL);
             updateSettings();
         }
 
@@ -1713,7 +1714,9 @@
 
         @Override
         public void run() {
-            takeScreenshot(mScreenshotType);
+            mScreenshotHelper.takeScreenshot(mScreenshotType,
+                    mStatusBar != null && mStatusBar.isVisibleLw(),
+                    mNavigationBar != null && mNavigationBar.isVisibleLw(), mHandler);
         }
     }
 
@@ -2191,6 +2194,7 @@
                         mWindowManagerFuncs.notifyKeyguardTrustedChanged();
                     }
                 });
+        mScreenshotHelper = new ScreenshotHelper(mContext);
     }
 
     /**
@@ -2303,9 +2307,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
@@ -2402,10 +2410,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);
@@ -2741,6 +2745,11 @@
     }
 
     @Override
+    public void onOverlayChangedLw() {
+        onConfigurationChanged();
+    }
+
+    @Override
     public void onConfigurationChanged() {
         // TODO(multi-display): Define policy for secondary displays.
         Context uiContext = getSystemUiContext();
@@ -4203,20 +4212,23 @@
             if (isKeyguardShowingAndNotOccluded()) {
                 // don't launch home if keyguard showing
                 return;
-            }
-
-            if (!mKeyguardOccluded && mKeyguardDelegate.isInputRestricted()) {
+            } else if (mKeyguardOccluded && mKeyguardDelegate.isShowing()) {
+                mKeyguardDelegate.dismiss(new KeyguardDismissCallback() {
+                    @Override
+                    public void onDismissSucceeded() throws RemoteException {
+                        mHandler.post(() -> {
+                            startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
+                        });
+                    }
+                }, null /* message */);
+                return;
+            } else if (!mKeyguardOccluded && mKeyguardDelegate.isInputRestricted()) {
                 // when in keyguard restricted mode, must first verify unlock
                 // before launching home
                 mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
                     @Override
                     public void onKeyguardExitResult(boolean success) {
                         if (success) {
-                            try {
-                                ActivityManager.getService().stopAppSwitches();
-                            } catch (RemoteException e) {
-                            }
-                            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
                             startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
                         }
                     }
@@ -4226,11 +4238,11 @@
         }
 
         // no keyguard stuff to worry about, just launch home!
-        try {
-            ActivityManager.getService().stopAppSwitches();
-        } catch (RemoteException e) {
-        }
         if (mRecentsVisible) {
+            try {
+                ActivityManager.getService().stopAppSwitches();
+            } catch (RemoteException e) {}
+
             // Hide Recents and notify it to launch Home
             if (awakenFromDreams) {
                 awakenDreams();
@@ -4238,7 +4250,6 @@
             hideRecentApps(false, true);
         } else {
             // Otherwise, just launch Home
-            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
             startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
         }
     }
@@ -4440,7 +4451,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();
@@ -4861,7 +4872,6 @@
 
         final int type = attrs.type;
         final int fl = PolicyControl.getWindowFlags(win, attrs);
-        final long fl2 = attrs.flags2;
         final int pfl = attrs.privateFlags;
         final int sim = attrs.softInputMode;
         final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(win, null);
@@ -4883,12 +4893,10 @@
         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;
-        final boolean layoutInCutout = (fl2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0;
 
         sf.set(displayFrames.mStable);
 
@@ -5048,9 +5056,6 @@
                         // moving from a window that is not hiding the status bar to one that is.
                         cf.set(displayFrames.mRestricted);
                     }
-                    if (requestedFullscreen && !layoutInCutout) {
-                        pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
-                    }
                     applyStableConstraints(sysUiFl, fl, cf, displayFrames);
                     if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
                         vf.set(displayFrames.mCurrent);
@@ -5136,9 +5141,6 @@
                     of.set(displayFrames.mUnrestricted);
                     df.set(displayFrames.mUnrestricted);
                     pf.set(displayFrames.mUnrestricted);
-                    if (requestedFullscreen && !layoutInCutout) {
-                        pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
-                    }
                 } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
                     of.set(displayFrames.mRestricted);
                     df.set(displayFrames.mRestricted);
@@ -5213,15 +5215,18 @@
             }
         }
 
-        // Ensure that windows that did not request to be laid out in the cutout don't get laid
-        // out there.
-        if (!layoutInCutout) {
+        final int cutoutMode = attrs.layoutInDisplayCutoutMode;
+        // Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in
+        // the cutout safe zone.
+        if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
             final Rect displayCutoutSafeExceptMaybeTop = mTmpRect;
             displayCutoutSafeExceptMaybeTop.set(displayFrames.mDisplayCutoutSafe);
-            if (layoutInScreen && layoutInsetDecor) {
+            if (layoutInScreen && layoutInsetDecor && !requestedFullscreen
+                    && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
                 // At the top we have the status bar, so apps that are
-                // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR already expect that there's an inset
-                // there and we don't need to exclude the window from that area.
+                // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN
+                // already expect that there's an inset there and we don't need to exclude
+                // the window from that area.
                 displayCutoutSafeExceptMaybeTop.top = Integer.MIN_VALUE;
             }
             pf.intersectUnchecked(displayCutoutSafeExceptMaybeTop);
@@ -5766,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
@@ -6948,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();
@@ -7725,6 +7636,11 @@
     }
 
     void startDockOrHome(boolean fromHomeKey, boolean awakenFromDreams) {
+        try {
+            ActivityManager.getService().stopAppSwitches();
+        } catch (RemoteException e) {}
+        sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+
         if (awakenFromDreams) {
             awakenDreams();
         }
@@ -7764,11 +7680,6 @@
         }
         if (false) {
             // This code always brings home to the front.
-            try {
-                ActivityManager.getService().stopAppSwitches();
-            } catch (RemoteException e) {
-            }
-            sendCloseSystemWindows();
             startDockOrHome(false /*fromHomeKey*/, true /* awakenFromDreams */);
         } else {
             // This code brings home to the front or, if it is already
@@ -7992,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;
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index cd3cabf..60dae53 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -138,11 +138,6 @@
  * </dl>
  */
 public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
-    // Navigation bar position values
-    int NAV_BAR_LEFT = 1 << 0;
-    int NAV_BAR_RIGHT = 1 << 1;
-    int NAV_BAR_BOTTOM = 1 << 2;
-
     @Retention(SOURCE)
     @IntDef({NAV_BAR_LEFT, NAV_BAR_RIGHT, NAV_BAR_BOTTOM})
     @interface NavigationBarPosition {}
@@ -176,6 +171,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 +446,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();
 
@@ -1385,8 +1392,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 1b5a521..48a196d 100644
--- a/services/core/java/com/android/server/policy/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java
@@ -32,6 +32,7 @@
 import android.view.Surface;
 
 import java.io.PrintWriter;
+import java.util.List;
 
 /**
  * A special helper class used by the WindowManager
@@ -90,7 +91,28 @@
         mHandler = handler;
         mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
         mRate = rate;
-        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_DEVICE_ORIENTATION);
+        List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION);
+        Sensor wakeUpDeviceOrientationSensor = null;
+        Sensor nonWakeUpDeviceOrientationSensor = null;
+        /**
+         *  Prefer the wakeup form of the sensor if implemented.
+         *  It's OK to look for just two types of this sensor and use
+         *  the last found. Typical devices will only have one sensor of
+         *  this type.
+         */
+        for (Sensor s : l) {
+            if (s.isWakeUpSensor()) {
+                wakeUpDeviceOrientationSensor = s;
+            } else {
+                nonWakeUpDeviceOrientationSensor = s;
+            }
+        }
+
+        if (wakeUpDeviceOrientationSensor != null) {
+            mSensor = wakeUpDeviceOrientationSensor;
+        } else {
+            mSensor = nonWakeUpDeviceOrientationSensor;
+        }
 
         if (mSensor != null) {
             mOrientationJudge = new OrientationSensorJudge();
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 6cb707a..18f4a3c 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -263,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);
         }
     }
 
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/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index d4b437a..f129fe1 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -75,6 +75,7 @@
 import android.view.AppTransitionAnimationSpec;
 import android.view.DisplayListCanvas;
 import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.RemoteAnimationAdapter;
 import android.view.RenderNode;
 import android.view.ThreadedRenderer;
 import android.view.WindowManager;
@@ -213,6 +214,8 @@
      * }.
      */
     private static final int NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS = 9;
+    private static final int NEXT_TRANSIT_TYPE_REMOTE = 10;
+
     private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
 
     // These are the possible states for the enter/exit activities during a thumbnail transition
@@ -275,6 +278,8 @@
     private final boolean mGridLayoutRecentsEnabled;
     private final boolean mLowRamRecentsEnabled;
 
+    private RemoteAnimationController mRemoteAnimationController;
+
     AppTransition(Context context, WindowManagerService service) {
         mContext = context;
         mService = service;
@@ -454,6 +459,9 @@
                 app.startDelayingAnimationStart();
             }
         }
+        if (mRemoteAnimationController != null) {
+            mRemoteAnimationController.goodToGo();
+        }
         return redoLayout;
     }
 
@@ -468,6 +476,7 @@
         mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
         mNextAppTransitionPackage = null;
         mNextAppTransitionAnimationsSpecs.clear();
+        mRemoteAnimationController = null;
         mNextAppTransitionAnimationsSpecsFuture = null;
         mDefaultNextAppTransitionAnimationSpec = null;
         mAnimationFinishedCallback = null;
@@ -1551,6 +1560,10 @@
                 && mNextAppTransition != TRANSIT_KEYGUARD_GOING_AWAY;
     }
 
+    RemoteAnimationController getRemoteAnimationController() {
+        return mRemoteAnimationController;
+    }
+
     /**
      *
      * @param frame These are the bounds of the window when it finishes the animation. This is where
@@ -1788,7 +1801,7 @@
 
     void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
             IRemoteCallback startedCallback) {
-        if (isTransitionSet()) {
+        if (canOverridePendingAppTransition()) {
             clear();
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
             mNextAppTransitionPackage = packageName;
@@ -1796,14 +1809,12 @@
             mNextAppTransitionExit = exitAnim;
             postAnimationCallback();
             mNextAppTransitionCallback = startedCallback;
-        } else {
-            postAnimationCallback();
         }
     }
 
     void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
             int startHeight) {
-        if (isTransitionSet()) {
+        if (canOverridePendingAppTransition()) {
             clear();
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_SCALE_UP;
             putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight, null);
@@ -1813,7 +1824,7 @@
 
     void overridePendingAppTransitionClipReveal(int startX, int startY,
                                                 int startWidth, int startHeight) {
-        if (isTransitionSet()) {
+        if (canOverridePendingAppTransition()) {
             clear();
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CLIP_REVEAL;
             putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight, null);
@@ -1823,7 +1834,7 @@
 
     void overridePendingAppTransitionThumb(GraphicBuffer srcThumb, int startX, int startY,
                                            IRemoteCallback startedCallback, boolean scaleUp) {
-        if (isTransitionSet()) {
+        if (canOverridePendingAppTransition()) {
             clear();
             mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP
                     : NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN;
@@ -1831,14 +1842,12 @@
             putDefaultNextAppTransitionCoordinates(startX, startY, 0, 0, srcThumb);
             postAnimationCallback();
             mNextAppTransitionCallback = startedCallback;
-        } else {
-            postAnimationCallback();
         }
     }
 
     void overridePendingAppTransitionAspectScaledThumb(GraphicBuffer srcThumb, int startX, int startY,
             int targetWidth, int targetHeight, IRemoteCallback startedCallback, boolean scaleUp) {
-        if (isTransitionSet()) {
+        if (canOverridePendingAppTransition()) {
             clear();
             mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
                     : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
@@ -1847,15 +1856,13 @@
                     srcThumb);
             postAnimationCallback();
             mNextAppTransitionCallback = startedCallback;
-        } else {
-            postAnimationCallback();
         }
     }
 
-    public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
+    void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
             IRemoteCallback onAnimationStartedCallback, IRemoteCallback onAnimationFinishedCallback,
             boolean scaleUp) {
-        if (isTransitionSet()) {
+        if (canOverridePendingAppTransition()) {
             clear();
             mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
                     : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
@@ -1878,15 +1885,13 @@
             postAnimationCallback();
             mNextAppTransitionCallback = onAnimationStartedCallback;
             mAnimationFinishedCallback = onAnimationFinishedCallback;
-        } else {
-            postAnimationCallback();
         }
     }
 
     void overridePendingAppTransitionMultiThumbFuture(
             IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
             boolean scaleUp) {
-        if (isTransitionSet()) {
+        if (canOverridePendingAppTransition()) {
             clear();
             mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
                     : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
@@ -1896,14 +1901,21 @@
         }
     }
 
-    void overrideInPlaceAppTransition(String packageName, int anim) {
+    void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter) {
         if (isTransitionSet()) {
             clear();
+            mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
+            mRemoteAnimationController = new RemoteAnimationController(mService,
+                    remoteAnimationAdapter, mService.mH);
+        }
+    }
+
+    void overrideInPlaceAppTransition(String packageName, int anim) {
+        if (canOverridePendingAppTransition()) {
+            clear();
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE;
             mNextAppTransitionPackage = packageName;
             mNextAppTransitionInPlace = anim;
-        } else {
-            postAnimationCallback();
         }
     }
 
@@ -1911,13 +1923,18 @@
      * @see {@link #NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS}
      */
     void overridePendingAppTransitionStartCrossProfileApps() {
-        if (isTransitionSet()) {
+        if (canOverridePendingAppTransition()) {
             clear();
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS;
             postAnimationCallback();
         }
     }
 
+    private boolean canOverridePendingAppTransition() {
+        // Remote animations always take precedence
+        return isTransitionSet() &&  mNextAppTransitionType != NEXT_TRANSIT_TYPE_REMOTE;
+    }
+
     /**
      * If a future is set for the app transition specs, fetch it in another thread.
      */
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index f2098dc..ed39159 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();
@@ -376,7 +378,6 @@
         // Reset the state of mHiddenSetFromTransferredStartingWindow since visibility is actually
         // been set by the app now.
         mHiddenSetFromTransferredStartingWindow = false;
-        setClientHidden(!visible);
 
         // Allow for state changes and animation to be applied if:
         // * token is transitioning visibility state
@@ -461,6 +462,12 @@
                 mService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(token);
             }
 
+            // Update the client visibility if we are not running an animation. Otherwise, we'll
+            // update client visibility state in onAnimationFinished.
+            if (!visible && !delayed) {
+                setClientHidden(true);
+            }
+
             // If we are hidden but there is no delay needed we immediately
             // apply the Surface transaction so that the ActivityManager
             // can have some guarantee on the Surface state following
@@ -678,6 +685,7 @@
         if (destroyedSomething) {
             final DisplayContent dc = getDisplayContent();
             dc.assignWindowLayers(true /*setLayoutNeeded*/);
+            updateLetterbox(null);
         }
     }
 
@@ -944,6 +952,7 @@
     void removeChild(WindowState child) {
         super.removeChild(child);
         checkKeyguardFlagsChanged();
+        updateLetterbox(child);
     }
 
     private boolean waitingForReplacement() {
@@ -1409,6 +1418,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
@@ -1580,27 +1616,37 @@
         // different animation is running.
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked");
         if (okToAnimate()) {
-            final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
-            if (a != null) {
-                final TaskStack stack = getStack();
-                mTmpPoint.set(0, 0);
-                mTmpRect.setEmpty();
-                if (stack != null) {
-                    stack.getRelativePosition(mTmpPoint);
-                    stack.getBounds(mTmpRect);
-                    mTmpRect.offsetTo(0, 0);
+            final AnimationAdapter adapter;
+            final TaskStack stack = getStack();
+            mTmpPoint.set(0, 0);
+            mTmpRect.setEmpty();
+            if (stack != null) {
+                stack.getRelativePosition(mTmpPoint);
+                stack.getBounds(mTmpRect);
+                mTmpRect.offsetTo(0, 0);
+            }
+            if (mService.mAppTransition.getRemoteAnimationController() != null) {
+                adapter = mService.mAppTransition.getRemoteAnimationController()
+                        .createAnimationAdapter(this, mTmpPoint, mTmpRect);
+            } else {
+                final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
+                if (a != null) {
+                    adapter = new LocalAnimationAdapter(
+                            new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
+                                    mService.mAppTransition.canSkipFirstFrame(),
+                                    mService.mAppTransition.getAppStackClipMode()),
+                            mService.mSurfaceAnimationRunner);
+                    if (a.getZAdjustment() == Animation.ZORDER_TOP) {
+                        mNeedsZBoost = true;
+                    }
+                    mTransit = transit;
+                    mTransitFlags = mService.mAppTransition.getTransitFlags();
+                } else {
+                    adapter = null;
                 }
-                final AnimationAdapter adapter = new LocalAnimationAdapter(
-                        new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
-                                mService.mAppTransition.canSkipFirstFrame(),
-                                mService.mAppTransition.getAppStackClipMode()),
-                        mService.mSurfaceAnimationRunner);
-                if (a.getZAdjustment() == Animation.ZORDER_TOP) {
-                    mNeedsZBoost = true;
-                }
+            }
+            if (adapter != null) {
                 startAnimation(getPendingTransaction(), adapter, !isVisible());
-                mTransit = transit;
-                mTransitFlags = mService.mAppTransition.getTransitFlags();
             }
         } else {
             cancelAnimation();
@@ -1635,6 +1681,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);
             }
@@ -1721,6 +1769,7 @@
                 "AppWindowToken");
 
         clearThumbnail();
+        setClientHidden(isHidden());
 
         if (mService.mInputMethodTarget != null && mService.mInputMethodTarget.mAppToken == this) {
             getDisplayContent().computeImeTarget(true /* updateImeTarget */);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4c5520f..a8e00dd 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -697,6 +697,7 @@
 
         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);
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/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 28b4c1d..0171b56 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -136,7 +136,7 @@
             outSurface.copyFrom(surface);
             final IBinder winBinder = window.asBinder();
             IBinder token = new Binder();
-            mDragState = new DragState(mService, token, surface, flags, winBinder);
+            mDragState = new DragState(mService, this, token, surface, flags, winBinder);
             mDragState.mPid = callerPid;
             mDragState.mUid = callerUid;
             mDragState.mOriginalAlpha = alpha;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index b9f437a..1ac9b88 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -42,6 +42,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.IUserManager;
+import android.os.UserManagerInternal;
 import android.util.Slog;
 import android.view.Display;
 import android.view.DragEvent;
@@ -55,6 +56,7 @@
 import android.view.animation.Interpolator;
 
 import com.android.internal.view.IDragAndDropPermissions;
+import com.android.server.LocalServices;
 import com.android.server.input.InputApplicationHandle;
 import com.android.server.input.InputWindowHandle;
 
@@ -116,10 +118,10 @@
     private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
     private Point mDisplaySize = new Point();
 
-    DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
-            int flags, IBinder localWin) {
+    DragState(WindowManagerService service, DragDropController controller, IBinder token,
+            SurfaceControl surface, int flags, IBinder localWin) {
         mService = service;
-        mDragDropController = service.mDragDropController;
+        mDragDropController = controller;
         mToken = token;
         mSurfaceControl = surface;
         mFlags = flags;
@@ -318,15 +320,9 @@
 
         mSourceUserId = UserHandle.getUserId(mUid);
 
-        final IUserManager userManager =
-                (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
-        try {
-            mCrossProfileCopyAllowed = !userManager.getUserRestrictions(mSourceUserId).getBoolean(
-                    UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
-        } catch (RemoteException e) {
-            Slog.e(TAG_WM, "Remote Exception calling UserManager: " + e);
-            mCrossProfileCopyAllowed = false;
-        }
+        final UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
+        mCrossProfileCopyAllowed = !userManager.getUserRestriction(
+                mSourceUserId, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
 
         if (DEBUG_DRAG) {
             Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
@@ -534,7 +530,8 @@
         final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
 
         final DragAndDropPermissionsHandler dragAndDropPermissions;
-        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
+        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
+                && mData != null) {
             dragAndDropPermissions = new DragAndDropPermissionsHandler(
                     mData,
                     mUid,
@@ -546,7 +543,9 @@
             dragAndDropPermissions = null;
         }
         if (mSourceUserId != targetUserId){
-            mData.fixUris(mSourceUserId);
+            if (mData != null) {
+                mData.fixUris(mSourceUserId);
+            }
         }
         final int myPid = Process.myPid();
         final IBinder token = touchedWin.mClient.asBinder();
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/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 69cbe46..62519e1 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -360,14 +360,19 @@
      * Sets the Ime state and height.
      */
     void setAdjustedForIme(boolean adjustedForIme, int imeHeight) {
-        // Return early if there is no state change
-        if (mIsImeShowing == adjustedForIme && mImeHeight == imeHeight) {
+        // Due to the order of callbacks from the system, we may receive an ime height even when
+        // {@param adjustedForIme} is false, and also a zero height when {@param adjustedForIme}
+        // is true.  Instead, ensure that the ime state changes with the height and if the ime is
+        // showing, then the height is non-zero.
+        final boolean imeShowing = adjustedForIme && imeHeight > 0;
+        imeHeight = imeShowing ? imeHeight : 0;
+        if (imeShowing == mIsImeShowing && imeHeight == mImeHeight) {
             return;
         }
 
-        mIsImeShowing = adjustedForIme;
+        mIsImeShowing = imeShowing;
         mImeHeight = imeHeight;
-        notifyImeVisibilityChanged(adjustedForIme, imeHeight);
+        notifyImeVisibilityChanged(imeShowing, imeHeight);
         notifyMovementBoundsChanged(true /* fromImeAdjustment */);
     }
 
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
new file mode 100644
index 0000000..688b4ff
--- /dev/null
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -0,0 +1,206 @@
+/*
+ * 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 com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationFinishedCallback.Stub;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+
+import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
+
+import java.util.ArrayList;
+
+/**
+ * Helper class to run app animations in a remote process.
+ */
+class RemoteAnimationController {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "RemoteAnimationController" : TAG_WM;
+    private static final long TIMEOUT_MS = 2000;
+
+    private final WindowManagerService mService;
+    private final RemoteAnimationAdapter mRemoteAnimationAdapter;
+    private final ArrayList<RemoteAnimationAdapterWrapper> mPendingAnimations = new ArrayList<>();
+    private final Rect mTmpRect = new Rect();
+    private final Handler mHandler;
+
+    private final IRemoteAnimationFinishedCallback mFinishedCallback = new Stub() {
+        @Override
+        public void onAnimationFinished() throws RemoteException {
+            RemoteAnimationController.this.onAnimationFinished();
+        }
+    };
+
+    private final Runnable mTimeoutRunnable = () -> {
+        onAnimationFinished();
+        invokeAnimationCancelled();
+    };
+
+    RemoteAnimationController(WindowManagerService service,
+            RemoteAnimationAdapter remoteAnimationAdapter, Handler handler) {
+        mService = service;
+        mRemoteAnimationAdapter = remoteAnimationAdapter;
+        mHandler = handler;
+    }
+
+    /**
+     * Creates an animation for each individual {@link AppWindowToken}.
+     *
+     * @param appWindowToken The app to animate.
+     * @param position The position app bounds, in screen coordinates.
+     * @param stackBounds The stack bounds of the app.
+     * @return The adapter to be run on the app.
+     */
+    AnimationAdapter createAnimationAdapter(AppWindowToken appWindowToken, Point position,
+            Rect stackBounds) {
+        final RemoteAnimationAdapterWrapper adapter = new RemoteAnimationAdapterWrapper(
+                appWindowToken, position, stackBounds);
+        mPendingAnimations.add(adapter);
+        return adapter;
+    }
+
+    /**
+     * Called when the transition is ready to be started, and all leashes have been set up.
+     */
+    void goodToGo() {
+        mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS);
+        try {
+            mRemoteAnimationAdapter.getRunner().onAnimationStart(createAnimations(),
+                    mFinishedCallback);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to start remote animation", e);
+            onAnimationFinished();
+        }
+    }
+
+    private RemoteAnimationTarget[] createAnimations() {
+        final RemoteAnimationTarget[] result = new RemoteAnimationTarget[mPendingAnimations.size()];
+        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+            result[i] = mPendingAnimations.get(i).createRemoteAppAnimation();
+        }
+        return result;
+    }
+
+    private void onAnimationFinished() {
+        mHandler.removeCallbacks(mTimeoutRunnable);
+        synchronized (mService.mWindowMap) {
+            mService.openSurfaceTransaction();
+            try {
+                for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+                    final RemoteAnimationAdapterWrapper adapter = mPendingAnimations.get(i);
+                    adapter.mCapturedFinishCallback.onAnimationFinished(adapter);
+                }
+            } finally {
+                mService.closeSurfaceTransaction("RemoteAnimationController#finished");
+            }
+        }
+    }
+
+    private void invokeAnimationCancelled() {
+        try {
+            mRemoteAnimationAdapter.getRunner().onAnimationCancelled();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to notify cancel", e);
+        }
+    }
+
+    private class RemoteAnimationAdapterWrapper implements AnimationAdapter {
+
+        private final AppWindowToken mAppWindowToken;
+        private SurfaceControl mCapturedLeash;
+        private OnAnimationFinishedCallback mCapturedFinishCallback;
+        private final Point mPosition = new Point();
+        private final Rect mStackBounds = new Rect();
+
+        RemoteAnimationAdapterWrapper(AppWindowToken appWindowToken, Point position,
+                Rect stackBounds) {
+            mAppWindowToken = appWindowToken;
+            mPosition.set(position.x, position.y);
+            mStackBounds.set(stackBounds);
+        }
+
+        RemoteAnimationTarget createRemoteAppAnimation() {
+            return new RemoteAnimationTarget(mAppWindowToken.getTask().mTaskId, getMode(),
+                    mCapturedLeash, !mAppWindowToken.fillsParent(),
+                    mAppWindowToken.findMainWindow().mWinAnimator.mLastClipRect,
+                    mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds);
+        }
+
+        private int getMode() {
+            if (mService.mOpeningApps.contains(mAppWindowToken)) {
+                return RemoteAnimationTarget.MODE_OPENING;
+            } else {
+                return RemoteAnimationTarget.MODE_CLOSING;
+            }
+        }
+
+        @Override
+        public boolean getDetachWallpaper() {
+            return false;
+        }
+
+        @Override
+        public int getBackgroundColor() {
+            return 0;
+        }
+
+        @Override
+        public void startAnimation(SurfaceControl animationLeash, Transaction t,
+                OnAnimationFinishedCallback finishCallback) {
+
+            // Restore z-layering, position and stack crop until client has a chance to modify it.
+            t.setLayer(animationLeash, mAppWindowToken.getPrefixOrderIndex());
+            t.setPosition(animationLeash, mPosition.x, mPosition.y);
+            mTmpRect.set(mStackBounds);
+            mTmpRect.offsetTo(0, 0);
+            t.setWindowCrop(animationLeash, mTmpRect);
+            mCapturedLeash = animationLeash;
+            mCapturedFinishCallback = finishCallback;
+        }
+
+        @Override
+        public void onAnimationCancelled(SurfaceControl animationLeash) {
+            mPendingAnimations.remove(this);
+            if (mPendingAnimations.isEmpty()) {
+                mHandler.removeCallbacks(mTimeoutRunnable);
+                invokeAnimationCancelled();
+            }
+        }
+
+        @Override
+        public long getDurationHint() {
+            return mRemoteAnimationAdapter.getDuration();
+        }
+
+        @Override
+        public long getStatusBarTransitionsStartTime() {
+            return SystemClock.uptimeMillis()
+                    + mRemoteAnimationAdapter.getStatusBarTransitionDelay();
+        }
+    }
+}
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/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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fcc9988..a91eb4f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
 import static android.Manifest.permission.MANAGE_APP_TOKENS;
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
@@ -210,6 +211,7 @@
 import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
+import android.view.RemoteAnimationAdapter;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Builder;
@@ -2624,6 +2626,18 @@
     }
 
     @Override
+    public void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter) {
+        if (!checkCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
+                "overridePendingAppTransitionRemote()")) {
+            throw new SecurityException(
+                    "Requires CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS permission");
+        }
+        synchronized (mWindowMap) {
+            mAppTransition.overridePendingAppTransitionRemote(remoteAnimationAdapter);
+        }
+    }
+
+    @Override
     public void endProlongedAnimations() {
         synchronized (mWindowMap) {
             for (final WindowState win : mWindowMap.values()) {
@@ -2941,10 +2955,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);
         }
     }
 
@@ -5894,6 +5908,7 @@
      * the screen is.
      * @see WindowManagerPolicy#getNavBarPosition()
      */
+    @Override
     @WindowManagerPolicy.NavigationBarPosition
     public int getNavBarPosition() {
         synchronized (mWindowMap) {
@@ -6596,6 +6611,14 @@
         }
     }
 
+    public void onOverlayChanged() {
+        synchronized (mWindowMap) {
+            mPolicy.onOverlayChangedLw();
+            mDisplayManagerInternal.onOverlayChanged();
+            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 97070bd..db30db0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -20,6 +20,7 @@
 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.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -30,6 +31,7 @@
 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;
@@ -39,6 +41,8 @@
 import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.FORMAT_CHANGED;
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -189,6 +193,7 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.input.InputWindowHandle;
 import com.android.server.policy.WindowManagerPolicy;
@@ -198,7 +203,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 +2981,33 @@
 
     /** @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.layoutInDisplayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
+            // Layout in cutout, no letterbox.
+            return false;
+        }
+        // TODO: handle dialogs and other non-filling windows
+        if (mAttrs.layoutInDisplayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER) {
+            // Never layout in cutout, always letterbox.
+            return true;
+        }
+        // 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() {
@@ -3923,6 +3953,22 @@
         return null;
     }
 
+    /**
+     * @return True if we our one of our ancestors has {@link #mAnimatingExit} set to true, false
+     *         otherwise.
+     */
+    @VisibleForTesting
+    boolean isSelfOrAncestorWindowAnimatingExit() {
+        WindowState window = this;
+        do {
+            if (window.mAnimatingExit) {
+                return true;
+            }
+            window = window.getParentWindow();
+        } while (window != null);
+        return false;
+    }
+
     void onExitAnimationDone() {
         if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this
                 + ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit
@@ -3958,7 +4004,7 @@
             mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
         }
 
-        if (!mAnimatingExit) {
+        if (!isSelfOrAncestorWindowAnimatingExit()) {
             return;
         }
 
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/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 0fbb11f..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;
 
@@ -103,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 3b0937b..0d9d85f 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;
@@ -151,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;
@@ -226,6 +226,7 @@
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -703,6 +704,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";
@@ -763,6 +791,7 @@
         private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
         private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
         private static final String TAG_IS_LOGOUT_ENABLED = "is_logout_enabled";
+        private static final String TAG_MANDATORY_BACKUP_TRANSPORT = "mandatory_backup_transport";
 
         DeviceAdminInfo info;
 
@@ -879,6 +908,10 @@
         // The blacklist data is stored in a file whose name is stored in the XML
         String passwordBlacklistFile = null;
 
+        // The component name of the backup transport which has to be used if backups are mandatory
+        // or null if backups are not mandatory.
+        ComponentName mandatoryBackupTransport = null;
+
         ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
             info = _info;
             isParent = parent;
@@ -1142,6 +1175,11 @@
                 out.attribute(null, ATTR_VALUE, Boolean.toString(isLogoutEnabled));
                 out.endTag(null, TAG_IS_LOGOUT_ENABLED);
             }
+            if (mandatoryBackupTransport != null) {
+                out.startTag(null, TAG_MANDATORY_BACKUP_TRANSPORT);
+                out.attribute(null, ATTR_VALUE, mandatoryBackupTransport.flattenToString());
+                out.endTag(null, TAG_MANDATORY_BACKUP_TRANSPORT);
+            }
         }
 
         void writePackageListToXml(XmlSerializer out, String outerTag,
@@ -1320,6 +1358,9 @@
                 } else if (TAG_IS_LOGOUT_ENABLED.equals(tag)) {
                     isLogoutEnabled = Boolean.parseBoolean(
                             parser.getAttributeValue(null, ATTR_VALUE));
+                } else if (TAG_MANDATORY_BACKUP_TRANSPORT.equals(tag)) {
+                    mandatoryBackupTransport = ComponentName.unflattenFromString(
+                            parser.getAttributeValue(null, ATTR_VALUE));
                 } else {
                     Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -1699,6 +1740,10 @@
             return ActivityManager.getService();
         }
 
+        ActivityManagerInternal getActivityManagerInternal() {
+            return LocalServices.getService(ActivityManagerInternal.class);
+        }
+
         IPackageManager getIPackageManager() {
             return AppGlobals.getPackageManager();
         }
@@ -1982,6 +2027,8 @@
         LocalServices.addService(DevicePolicyManagerInternal.class, mLocalService);
 
         mSetupContentObserver = new SetupContentObserver(mHandler);
+
+        mUserManagerInternal.addUserRestrictionsListener(new RestrictionsListener(mContext));
     }
 
     /**
@@ -4470,6 +4517,28 @@
         }
     }
 
+    private boolean canPOorDOCallResetPassword(ActiveAdmin admin, @UserIdInt int userId) {
+        // Only if the admins targets a pre-O SDK
+        return getTargetSdk(admin.info.getPackageName(), userId) < Build.VERSION_CODES.O;
+    }
+
+    /* PO or DO could do an untrusted reset in certain conditions. */
+    private boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId) {
+        synchronized (this) {
+            // An active DO or PO might be able to fo an untrusted credential reset
+            for (final ActiveAdmin admin : getUserData(userId).mAdminList) {
+                if (!isActiveAdminWithPolicyForUserLocked(admin,
+                          DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, userId)) {
+                    continue;
+                }
+                if (canPOorDOCallResetPassword(admin, userId)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
     @Override
     public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
         final int callingUid = mInjector.binderGetCallingUid();
@@ -4488,12 +4557,12 @@
                     null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, callingUid);
             final boolean preN;
             if (admin != null) {
-                final int targetSdk = getTargetSdk(admin.info.getPackageName(), userHandle);
-                if (targetSdk >= Build.VERSION_CODES.O) {
+                if (!canPOorDOCallResetPassword(admin, userHandle)) {
                     throw new SecurityException("resetPassword() is deprecated for DPC targeting O"
                             + " or later");
                 }
-                preN = targetSdk <= android.os.Build.VERSION_CODES.M;
+                preN = getTargetSdk(admin.info.getPackageName(),
+                        userHandle) <= android.os.Build.VERSION_CODES.M;
             } else {
                 // Otherwise, make sure the caller has any active admin with the right policy.
                 admin = getActiveAdminForCallerLocked(null,
@@ -7286,6 +7355,7 @@
         policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
         policy.mAffiliationIds.clear();
         policy.mLockTaskPackages.clear();
+        updateLockTaskPackagesLocked(policy.mLockTaskPackages, userId);
         policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
         saveSettingsLocked(userId);
 
@@ -8611,14 +8681,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);
@@ -8631,6 +8693,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);
         }
@@ -8669,6 +8733,7 @@
     @Override
     public boolean switchUser(ComponentName who, UserHandle userHandle) {
         Preconditions.checkNotNull(who, "ComponentName is null");
+
         synchronized (this) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
 
@@ -8689,8 +8754,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);
@@ -9739,7 +9836,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 {
@@ -10036,6 +10133,11 @@
                 updateMaximumTimeToLockLocked(userId);
             }
         }
+
+        @Override
+        public boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId) {
+            return DevicePolicyManagerService.this.canUserHaveUntrustedCredentialReset(userId);
+        }
     }
 
     private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
@@ -11277,7 +11379,12 @@
         }
         Preconditions.checkNotNull(admin);
         synchronized (this) {
-            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(
+                    admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            if (!enabled) {
+                activeAdmin.mandatoryBackupTransport = null;
+                saveSettingsLocked(UserHandle.USER_SYSTEM);
+            }
         }
 
         final long ident = mInjector.binderClearCallingIdentity();
@@ -11312,6 +11419,50 @@
     }
 
     @Override
+    public void setMandatoryBackupTransport(
+            ComponentName admin, ComponentName backupTransportComponent) {
+        if (!mHasFeature) {
+            return;
+        }
+        Preconditions.checkNotNull(admin);
+        synchronized (this) {
+            ActiveAdmin activeAdmin =
+                    getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            if (!Objects.equals(backupTransportComponent, activeAdmin.mandatoryBackupTransport)) {
+                activeAdmin.mandatoryBackupTransport = backupTransportComponent;
+                saveSettingsLocked(UserHandle.USER_SYSTEM);
+            }
+        }
+        final long identity = mInjector.binderClearCallingIdentity();
+        try {
+            IBackupManager ibm = mInjector.getIBackupManager();
+            if (ibm != null && backupTransportComponent != null) {
+                if (!ibm.isBackupServiceActive(UserHandle.USER_SYSTEM)) {
+                    ibm.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+                }
+                ibm.selectBackupTransportAsync(backupTransportComponent, null);
+                ibm.setBackupEnabled(true);
+            }
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Failed to set mandatory backup transport.", e);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public ComponentName getMandatoryBackupTransport() {
+        if (!mHasFeature) {
+            return null;
+        }
+        synchronized (this) {
+            ActiveAdmin activeAdmin = getDeviceOwnerAdminLocked();
+            return activeAdmin == null ? null : activeAdmin.mandatoryBackupTransport;
+        }
+    }
+
+
+    @Override
     public boolean bindDeviceAdminServiceAsUser(
             @NonNull ComponentName admin, @NonNull IApplicationThread caller,
             @Nullable IBinder activtiyToken, @NonNull Intent serviceIntent,
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4310a98..3199bfa 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -80,6 +80,7 @@
 import com.android.server.lights.LightsService;
 import com.android.server.media.MediaResourceMonitorService;
 import com.android.server.media.MediaRouterService;
+import com.android.server.media.MediaUpdateService;
 import com.android.server.media.MediaSessionService;
 import com.android.server.media.projection.MediaProjectionManagerService;
 import com.android.server.net.NetworkPolicyManagerService;
@@ -1442,6 +1443,10 @@
             mSystemServiceManager.startService(MediaSessionService.class);
             traceEnd();
 
+            traceBeginAndSlog("StartMediaUpdateService");
+            mSystemServiceManager.startService(MediaUpdateService.class);
+            traceEnd();
+
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
                 traceBeginAndSlog("StartHdmiControlService");
                 mSystemServiceManager.startService(HdmiControlService.class);
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 71ba685..1dc9d26 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -57,7 +57,9 @@
 
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.print.DualDumpOutputStream;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemService;
 
@@ -670,20 +672,24 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 if (dumpAsProto) {
-                    dump(new ProtoOutputStream(fd), userStatesToDump);
+                    dump(new DualDumpOutputStream(new ProtoOutputStream(fd), null),
+                            userStatesToDump);
                 } else {
-                    dump(fd, pw, userStatesToDump);
+                    pw.println("PRINT MANAGER STATE (dumpsys print)");
+
+                    dump(new DualDumpOutputStream(null, new IndentingPrintWriter(pw, "  ")),
+                            userStatesToDump);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
         }
 
-        private void dump(@NonNull ProtoOutputStream proto,
+        private void dump(@NonNull DualDumpOutputStream proto,
                 @NonNull ArrayList<UserState> userStatesToDump) {
             final int userStateCount = userStatesToDump.size();
             for (int i = 0; i < userStateCount; i++) {
-                long token = proto.start(PrintServiceDumpProto.USER_STATES);
+                long token = proto.start("user_states", PrintServiceDumpProto.USER_STATES);
                 userStatesToDump.get(i).dump(proto);
                 proto.end(token);
             }
@@ -691,18 +697,6 @@
             proto.flush();
         }
 
-        private void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
-                @NonNull ArrayList<UserState> userStatesToDump) {
-            pw = Preconditions.checkNotNull(pw);
-
-            pw.println("PRINT MANAGER STATE (dumpsys print)");
-            final int userStateCount = userStatesToDump.size();
-            for (int i = 0; i < userStateCount; i++) {
-                userStatesToDump.get(i).dump(fd, pw, "");
-                pw.println();
-            }
-        }
-
         private void registerContentObservers() {
             final Uri enabledPrintServicesUri = Settings.Secure.getUriFor(
                     Settings.Secure.DISABLED_PRINT_SERVICES);
diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java
index 13462cd..80b97cf 100644
--- a/services/print/java/com/android/server/print/RemotePrintService.java
+++ b/services/print/java/com/android/server/print/RemotePrintService.java
@@ -47,11 +47,10 @@
 import android.printservice.IPrintServiceClient;
 import android.service.print.ActivePrintServiceProto;
 import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.print.DualDumpOutputStream;
 
-import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
@@ -532,49 +531,30 @@
         }
     }
 
-    public void dump(@NonNull ProtoOutputStream proto) {
-        writeComponentName(proto, ActivePrintServiceProto.COMPONENT_NAME, mComponentName);
+    public void dump(@NonNull DualDumpOutputStream proto) {
+        writeComponentName(proto, "component_name", ActivePrintServiceProto.COMPONENT_NAME,
+                mComponentName);
 
-        proto.write(ActivePrintServiceProto.IS_DESTROYED, mDestroyed);
-        proto.write(ActivePrintServiceProto.IS_BOUND, isBound());
-        proto.write(ActivePrintServiceProto.HAS_DISCOVERY_SESSION, mHasPrinterDiscoverySession);
-        proto.write(ActivePrintServiceProto.HAS_ACTIVE_PRINT_JOBS, mHasActivePrintJobs);
-        proto.write(ActivePrintServiceProto.IS_DISCOVERING_PRINTERS,
+        proto.write("is_destroyed", ActivePrintServiceProto.IS_DESTROYED, mDestroyed);
+        proto.write("is_bound", ActivePrintServiceProto.IS_BOUND, isBound());
+        proto.write("has_discovery_session", ActivePrintServiceProto.HAS_DISCOVERY_SESSION,
+                mHasPrinterDiscoverySession);
+        proto.write("has_active_print_jobs", ActivePrintServiceProto.HAS_ACTIVE_PRINT_JOBS,
+                mHasActivePrintJobs);
+        proto.write("is_discovering_printers", ActivePrintServiceProto.IS_DISCOVERING_PRINTERS,
                 mDiscoveryPriorityList != null);
 
         synchronized (mLock) {
             if (mTrackedPrinterList != null) {
                 int numTrackedPrinters = mTrackedPrinterList.size();
                 for (int i = 0; i < numTrackedPrinters; i++) {
-                    writePrinterId(proto, ActivePrintServiceProto.TRACKED_PRINTERS,
-                            mTrackedPrinterList.get(i));
+                    writePrinterId(proto, "tracked_printers",
+                            ActivePrintServiceProto.TRACKED_PRINTERS, mTrackedPrinterList.get(i));
                 }
             }
         }
     }
 
-    public void dump(PrintWriter pw, String prefix) {
-        String tab = "  ";
-        pw.append(prefix).append("service:").println();
-        pw.append(prefix).append(tab).append("componentName=")
-                .append(mComponentName.flattenToString()).println();
-        pw.append(prefix).append(tab).append("destroyed=")
-                .append(String.valueOf(mDestroyed)).println();
-        pw.append(prefix).append(tab).append("bound=")
-                .append(String.valueOf(isBound())).println();
-        pw.append(prefix).append(tab).append("hasDicoverySession=")
-                .append(String.valueOf(mHasPrinterDiscoverySession)).println();
-        pw.append(prefix).append(tab).append("hasActivePrintJobs=")
-                .append(String.valueOf(mHasActivePrintJobs)).println();
-        pw.append(prefix).append(tab).append("isDiscoveringPrinters=")
-                .append(String.valueOf(mDiscoveryPriorityList != null)).println();
-
-        synchronized (mLock) {
-            pw.append(prefix).append(tab).append("trackedPrinters=").append(
-                    (mTrackedPrinterList != null) ? mTrackedPrinterList.toString() : "null");
-        }
-    }
-
     private boolean isBound() {
         return mPrintService != null;
     }
diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java
index f654fcb..5520255 100644
--- a/services/print/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java
@@ -43,16 +43,14 @@
 import android.service.print.PrintSpoolerStateProto;
 import android.util.Slog;
 import android.util.TimedRemoteCaller;
-import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.TransferPipe;
+import com.android.internal.print.DualDumpOutputStream;
 
 import libcore.io.IoUtils;
 
-import java.io.FileDescriptor;
 import java.io.IOException;
-import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
@@ -558,37 +556,25 @@
         }
     }
 
-    public void dump(@NonNull ProtoOutputStream proto) {
+    public void dump(@NonNull DualDumpOutputStream proto) {
         synchronized (mLock) {
-            proto.write(PrintSpoolerStateProto.IS_DESTROYED, mDestroyed);
-            proto.write(PrintSpoolerStateProto.IS_BOUND, mRemoteInstance != null);
+            proto.write("is_destroyed", PrintSpoolerStateProto.IS_DESTROYED, mDestroyed);
+            proto.write("is_bound", PrintSpoolerStateProto.IS_BOUND, mRemoteInstance != null);
         }
 
         try {
-            proto.write(PrintSpoolerStateProto.INTERNAL_STATE,
-                    TransferPipe.dumpAsync(getRemoteInstanceLazy().asBinder(), "--proto"));
+            if (proto.isProto()) {
+                proto.write(null, PrintSpoolerStateProto.INTERNAL_STATE,
+                        TransferPipe.dumpAsync(getRemoteInstanceLazy().asBinder(), "--proto"));
+            } else {
+                proto.writeNested("internal_state", TransferPipe.dumpAsync(
+                        getRemoteInstanceLazy().asBinder()));
+            }
         } catch (IOException | TimeoutException | RemoteException | InterruptedException e) {
             Slog.e(LOG_TAG, "Failed to dump remote instance", e);
         }
     }
 
-    public void dump(FileDescriptor fd, PrintWriter pw, String prefix) {
-        synchronized (mLock) {
-            pw.append(prefix).append("destroyed=")
-                    .append(String.valueOf(mDestroyed)).println();
-            pw.append(prefix).append("bound=")
-                    .append((mRemoteInstance != null) ? "true" : "false").println();
-
-            pw.flush();
-            try {
-                TransferPipe.dumpAsync(getRemoteInstanceLazy().asBinder(), fd,
-                        new String[] { prefix });
-            } catch (IOException | TimeoutException | RemoteException | InterruptedException e) {
-                pw.println("Failed to dump remote instance: " + e);
-            }
-        }
-    }
-
     private void onAllPrintJobsHandled() {
         synchronized (mLock) {
             throwIfDestroyedLocked();
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 364bbc0..318e481 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -76,19 +76,17 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.print.DualDumpOutputStream;
 import com.android.server.print.RemotePrintService.PrintServiceCallbacks;
 import com.android.server.print.RemotePrintServiceRecommendationService
         .RemotePrintServiceRecommendationServiceCallbacks;
 import com.android.server.print.RemotePrintSpooler.PrintSpoolerCallbacks;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -817,37 +815,43 @@
         mDestroyed = true;
     }
 
-    public void dump(@NonNull ProtoOutputStream proto) {
+    public void dump(@NonNull DualDumpOutputStream proto) {
         synchronized (mLock) {
-            proto.write(PrintUserStateProto.USER_ID, mUserId);
+            proto.write("user_id", PrintUserStateProto.USER_ID, mUserId);
 
             final int installedServiceCount = mInstalledServices.size();
             for (int i = 0; i < installedServiceCount; i++) {
-                long token = proto.start(PrintUserStateProto.INSTALLED_SERVICES);
+                long token = proto.start("installed_services",
+                        PrintUserStateProto.INSTALLED_SERVICES);
                 PrintServiceInfo installedService = mInstalledServices.get(i);
 
                 ResolveInfo resolveInfo = installedService.getResolveInfo();
-                writeComponentName(proto, InstalledPrintServiceProto.COMPONENT_NAME,
+                writeComponentName(proto, "component_name",
+                        InstalledPrintServiceProto.COMPONENT_NAME,
                         new ComponentName(resolveInfo.serviceInfo.packageName,
                                 resolveInfo.serviceInfo.name));
 
-                writeStringIfNotNull(proto, InstalledPrintServiceProto.SETTINGS_ACTIVITY,
+                writeStringIfNotNull(proto, "settings_activity",
+                        InstalledPrintServiceProto.SETTINGS_ACTIVITY,
                         installedService.getSettingsActivityName());
-                writeStringIfNotNull(proto, InstalledPrintServiceProto.ADD_PRINTERS_ACTIVITY,
+                writeStringIfNotNull(proto, "add_printers_activity",
+                        InstalledPrintServiceProto.ADD_PRINTERS_ACTIVITY,
                         installedService.getAddPrintersActivityName());
-                writeStringIfNotNull(proto, InstalledPrintServiceProto.ADVANCED_OPTIONS_ACTIVITY,
+                writeStringIfNotNull(proto, "advanced_options_activity",
+                        InstalledPrintServiceProto.ADVANCED_OPTIONS_ACTIVITY,
                         installedService.getAdvancedOptionsActivityName());
 
                 proto.end(token);
             }
 
             for (ComponentName disabledService : mDisabledServices) {
-                writeComponentName(proto, PrintUserStateProto.DISABLED_SERVICES, disabledService);
+                writeComponentName(proto, "disabled_services",
+                        PrintUserStateProto.DISABLED_SERVICES, disabledService);
             }
 
             final int activeServiceCount = mActiveServices.size();
             for (int i = 0; i < activeServiceCount; i++) {
-                long token = proto.start(PrintUserStateProto.ACTIVE_SERVICES);
+                long token = proto.start("actives_services", PrintUserStateProto.ACTIVE_SERVICES);
                 mActiveServices.valueAt(i).dump(proto);
                 proto.end(token);
             }
@@ -855,76 +859,19 @@
             mPrintJobForAppCache.dumpLocked(proto);
 
             if (mPrinterDiscoverySession != null) {
-                long token = proto.start(PrintUserStateProto.DISCOVERY_SESSIONS);
+                long token = proto.start("discovery_service",
+                        PrintUserStateProto.DISCOVERY_SESSIONS);
                 mPrinterDiscoverySession.dumpLocked(proto);
                 proto.end(token);
             }
 
         }
 
-        long token = proto.start(PrintUserStateProto.PRINT_SPOOLER_STATE);
+        long token = proto.start("print_spooler_state", PrintUserStateProto.PRINT_SPOOLER_STATE);
         mSpooler.dump(proto);
         proto.end(token);
     }
 
-    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String prefix) {
-        pw.append(prefix).append("user state ").append(String.valueOf(mUserId)).append(":");
-        pw.println();
-
-        String tab = "  ";
-
-        synchronized (mLock) {
-            pw.append(prefix).append(tab).append("installed services:").println();
-            final int installedServiceCount = mInstalledServices.size();
-            for (int i = 0; i < installedServiceCount; i++) {
-                PrintServiceInfo installedService = mInstalledServices.get(i);
-                String installedServicePrefix = prefix + tab + tab;
-                pw.append(installedServicePrefix).append("service:").println();
-                ResolveInfo resolveInfo = installedService.getResolveInfo();
-                ComponentName componentName = new ComponentName(
-                        resolveInfo.serviceInfo.packageName,
-                        resolveInfo.serviceInfo.name);
-                pw.append(installedServicePrefix).append(tab).append("componentName=")
-                        .append(componentName.flattenToString()).println();
-                pw.append(installedServicePrefix).append(tab).append("settingsActivity=")
-                        .append(installedService.getSettingsActivityName()).println();
-                pw.append(installedServicePrefix).append(tab).append("addPrintersActivity=")
-                        .append(installedService.getAddPrintersActivityName()).println();
-                pw.append(installedServicePrefix).append(tab).append("avancedOptionsActivity=")
-                        .append(installedService.getAdvancedOptionsActivityName()).println();
-            }
-
-            pw.append(prefix).append(tab).append("disabled services:").println();
-            for (ComponentName disabledService : mDisabledServices) {
-                String disabledServicePrefix = prefix + tab + tab;
-                pw.append(disabledServicePrefix).append("service:").println();
-                pw.append(disabledServicePrefix).append(tab).append("componentName=")
-                        .append(disabledService.flattenToString());
-                pw.println();
-            }
-
-            pw.append(prefix).append(tab).append("active services:").println();
-            final int activeServiceCount = mActiveServices.size();
-            for (int i = 0; i < activeServiceCount; i++) {
-                RemotePrintService activeService = mActiveServices.valueAt(i);
-                activeService.dump(pw, prefix + tab + tab);
-                pw.println();
-            }
-
-            pw.append(prefix).append(tab).append("cached print jobs:").println();
-            mPrintJobForAppCache.dumpLocked(pw, prefix + tab + tab);
-
-            pw.append(prefix).append(tab).append("discovery mediator:").println();
-            if (mPrinterDiscoverySession != null) {
-                mPrinterDiscoverySession.dumpLocked(pw, prefix + tab + tab);
-            }
-        }
-
-        pw.append(prefix).append(tab).append("print spooler:").println();
-        mSpooler.dump(fd, pw, prefix + tab + tab);
-        pw.println();
-    }
-
     private void readConfigurationLocked() {
         readInstalledPrintServicesLocked();
         readDisabledPrintServicesLocked();
@@ -1650,15 +1597,17 @@
             }
         }
 
-        public void dumpLocked(@NonNull ProtoOutputStream proto) {
-            proto.write(PrinterDiscoverySessionProto.IS_DESTROYED, mDestroyed);
-            proto.write(PrinterDiscoverySessionProto.IS_PRINTER_DISCOVERY_IN_PROGRESS,
+        public void dumpLocked(@NonNull DualDumpOutputStream proto) {
+            proto.write("is_destroyed", PrinterDiscoverySessionProto.IS_DESTROYED, mDestroyed);
+            proto.write("is_printer_discovery_in_progress",
+                    PrinterDiscoverySessionProto.IS_PRINTER_DISCOVERY_IN_PROGRESS,
                     !mStartedPrinterDiscoveryTokens.isEmpty());
 
             final int observerCount = mDiscoveryObservers.beginBroadcast();
             for (int i = 0; i < observerCount; i++) {
                 IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
-                proto.write(PrinterDiscoverySessionProto.PRINTER_DISCOVERY_OBSERVERS,
+                proto.write("printer_discovery_observers",
+                        PrinterDiscoverySessionProto.PRINTER_DISCOVERY_OBSERVERS,
                         observer.toString());
             }
             mDiscoveryObservers.finishBroadcast();
@@ -1666,61 +1615,22 @@
             final int tokenCount = this.mStartedPrinterDiscoveryTokens.size();
             for (int i = 0; i < tokenCount; i++) {
                 IBinder token = mStartedPrinterDiscoveryTokens.get(i);
-                proto.write(PrinterDiscoverySessionProto.DISCOVERY_REQUESTS, token.toString());
+                proto.write("discovery_requests",
+                        PrinterDiscoverySessionProto.DISCOVERY_REQUESTS, token.toString());
             }
 
             final int trackedPrinters = mStateTrackedPrinters.size();
             for (int i = 0; i < trackedPrinters; i++) {
                 PrinterId printer = mStateTrackedPrinters.get(i);
-                writePrinterId(proto, PrinterDiscoverySessionProto.TRACKED_PRINTER_REQUESTS,
-                        printer);
+                writePrinterId(proto, "tracked_printer_requests",
+                        PrinterDiscoverySessionProto.TRACKED_PRINTER_REQUESTS, printer);
             }
 
             final int printerCount = mPrinters.size();
             for (int i = 0; i < printerCount; i++) {
                 PrinterInfo printer = mPrinters.valueAt(i);
-                writePrinterInfo(mContext, proto, PrinterDiscoverySessionProto.PRINTER, printer);
-            }
-        }
-
-        public void dumpLocked(PrintWriter pw, String prefix) {
-            pw.append(prefix).append("destroyed=")
-                    .append(String.valueOf(mDestroyed)).println();
-
-            pw.append(prefix).append("printDiscoveryInProgress=")
-                    .append(String.valueOf(!mStartedPrinterDiscoveryTokens.isEmpty())).println();
-
-            String tab = "  ";
-
-            pw.append(prefix).append(tab).append("printer discovery observers:").println();
-            final int observerCount = mDiscoveryObservers.beginBroadcast();
-            for (int i = 0; i < observerCount; i++) {
-                IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i);
-                pw.append(prefix).append(prefix).append(observer.toString());
-                pw.println();
-            }
-            mDiscoveryObservers.finishBroadcast();
-
-            pw.append(prefix).append(tab).append("start discovery requests:").println();
-            final int tokenCount = this.mStartedPrinterDiscoveryTokens.size();
-            for (int i = 0; i < tokenCount; i++) {
-                IBinder token = mStartedPrinterDiscoveryTokens.get(i);
-                pw.append(prefix).append(tab).append(tab).append(token.toString()).println();
-            }
-
-            pw.append(prefix).append(tab).append("tracked printer requests:").println();
-            final int trackedPrinters = mStateTrackedPrinters.size();
-            for (int i = 0; i < trackedPrinters; i++) {
-                PrinterId printer = mStateTrackedPrinters.get(i);
-                pw.append(prefix).append(tab).append(tab).append(printer.toString()).println();
-            }
-
-            pw.append(prefix).append(tab).append("printers:").println();
-            final int pritnerCount = mPrinters.size();
-            for (int i = 0; i < pritnerCount; i++) {
-                PrinterInfo printer = mPrinters.valueAt(i);
-                pw.append(prefix).append(tab).append(tab).append(
-                        printer.toString()).println();
+                writePrinterInfo(mContext, proto, "printer",
+                        PrinterDiscoverySessionProto.PRINTER, printer);
             }
         }
 
@@ -1933,33 +1843,19 @@
             }
         }
 
-        public void dumpLocked(PrintWriter pw, String prefix) {
-            String tab = "  ";
-            final int bucketCount = mPrintJobsForRunningApp.size();
-            for (int i = 0; i < bucketCount; i++) {
-                final int appId = mPrintJobsForRunningApp.keyAt(i);
-                pw.append(prefix).append("appId=" + appId).append(':').println();
-                List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
-                final int printJobCount = bucket.size();
-                for (int j = 0; j < printJobCount; j++) {
-                    PrintJobInfo printJob = bucket.get(j);
-                    pw.append(prefix).append(tab).append(printJob.toString()).println();
-                }
-            }
-        }
-
-        public void dumpLocked(@NonNull ProtoOutputStream proto) {
+        public void dumpLocked(@NonNull DualDumpOutputStream proto) {
             final int bucketCount = mPrintJobsForRunningApp.size();
             for (int i = 0; i < bucketCount; i++) {
                 final int appId = mPrintJobsForRunningApp.keyAt(i);
                 List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
                 final int printJobCount = bucket.size();
                 for (int j = 0; j < printJobCount; j++) {
-                    long token = proto.start(PrintUserStateProto.CACHED_PRINT_JOBS);
+                    long token = proto.start("cached_print_jobs",
+                            PrintUserStateProto.CACHED_PRINT_JOBS);
 
-                    proto.write(CachedPrintJobProto.APP_ID, appId);
+                    proto.write("app_id", CachedPrintJobProto.APP_ID, appId);
 
-                    writePrintJobInfo(mContext, proto, CachedPrintJobProto.PRINT_JOB,
+                    writePrintJobInfo(mContext, proto, "print_job", CachedPrintJobProto.PRINT_JOB,
                             bucket.get(j));
 
                     proto.end(token);
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index f9ebd28..4ff24e9 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,9 @@
 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.ShadowBackupPolicyEnforcer;
+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,6 +63,7 @@
 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;
@@ -67,28 +74,30 @@
 @Config(
     manifest = Config.NONE,
     sdk = 26,
-    shadows = {ShadowAppBackupUtils.class}
+    shadows = {ShadowAppBackupUtils.class, ShadowBackupPolicyEnforcer.class}
 )
 @SystemLoaderClasses({RefactoredBackupManagerService.class, TransportManager.class})
 @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));
@@ -103,20 +112,14 @@
         mBaseStateDir = new File(cacheDir, "base_state_dir");
         mDataDir = new File(cacheDir, "data_dir");
 
-        mBackupManagerService =
-                new RefactoredBackupManagerService(
-                        mContext,
-                        new Trampoline(mContext),
-                        mBackupThread,
-                        mBaseStateDir,
-                        mDataDir,
-                        mTransportManager);
+        ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(null);
     }
 
     @After
     public void tearDown() throws Exception {
         mBackupThread.quit();
         ShadowAppBackupUtils.reset();
+        ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(null);
     }
 
     /* Tests for destination string */
@@ -124,10 +127,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 +140,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 +153,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 +168,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 +197,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,28 +242,35 @@
     @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"}));
     }
 
     /* Tests for select transport */
 
-    private TransportData mNewTransport;
-    private TransportData mOldTransport;
     private ComponentName mNewTransportComponent;
-    private ISelectBackupTransportCallback mCallback;
+    private TransportData mNewTransport;
+    private TransportMock mNewTransportMock;
+    private ComponentName mOldTransportComponent;
+    private TransportData mOldTransport;
+    private TransportMock mOldTransportMock;
 
     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();
+        mOldTransportComponent = mOldTransport.getTransportComponent();
+        List<TransportMock> transportMocks =
+                setUpTransports(mTransportManager, mNewTransport, mOldTransport, localTransport());
+        mNewTransportMock = transportMocks.get(0);
+        mOldTransportMock = transportMocks.get(1);
         when(mTransportManager.selectTransport(eq(mNewTransport.transportName)))
                 .thenReturn(mOldTransport.transportName);
     }
@@ -254,22 +279,28 @@
     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);
+        verify(mTransportManager)
+                .disposeOfTransportClient(eq(mNewTransportMock.transportClient), any());
     }
 
     @Test
     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,13 +309,55 @@
         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);
         verify(callback).onSuccess(eq(mNewTransport.transportName));
+        verify(mTransportManager)
+                .disposeOfTransportClient(eq(mNewTransportMock.transportClient), any());
+    }
+
+    @Test
+    public void testSelectBackupTransportAsync_whenMandatoryTransport() throws Exception {
+        setUpForSelectTransport();
+        ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(mNewTransportComponent);
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent)))
+                .thenReturn(BackupManager.SUCCESS);
+        ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
+
+        backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
+
+        mShadowBackupLooper.runToEndOfTasks();
+        assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName);
+        verify(callback).onSuccess(eq(mNewTransport.transportName));
+        verify(mTransportManager)
+                .disposeOfTransportClient(eq(mNewTransportMock.transportClient), any());
+    }
+
+    @Test
+    public void testSelectBackupTransportAsync_whenOtherThanMandatoryTransport() throws Exception {
+        setUpForSelectTransport();
+        ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(mOldTransportComponent);
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent)))
+                .thenReturn(BackupManager.SUCCESS);
+        ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
+
+        backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
+
+        mShadowBackupLooper.runToEndOfTasks();
+        assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName);
+        verify(callback).onFailure(eq(BackupManager.ERROR_BACKUP_NOT_ALLOWED));
     }
 
     @Test
@@ -293,9 +366,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 +379,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 +399,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 +414,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/ShadowBackupPolicyEnforcer.java b/services/robotests/src/com/android/server/backup/testing/ShadowBackupPolicyEnforcer.java
new file mode 100644
index 0000000..88b30da
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/testing/ShadowBackupPolicyEnforcer.java
@@ -0,0 +1,24 @@
+package com.android.server.backup.testing;
+
+import android.content.ComponentName;
+
+import com.android.server.backup.BackupPolicyEnforcer;
+import com.android.server.backup.RefactoredBackupManagerService;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(BackupPolicyEnforcer.class)
+public class ShadowBackupPolicyEnforcer {
+
+    private static ComponentName sMandatoryBackupTransport;
+
+    public static void setMandatoryBackupTransport(ComponentName backupTransportComponent) {
+        sMandatoryBackupTransport = backupTransportComponent;
+    }
+
+    @Implementation
+    public ComponentName getMandatoryBackupTransport() {
+        return sMandatoryBackupTransport;
+    }
+}
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..10442b7 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,12 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
-
 import com.android.internal.backup.IBackupTransport;
+import com.android.server.EventLogTags;
 import com.android.server.backup.TransportManager;
 import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.ShadowEventLog;
 import com.android.server.testing.SystemLoaderClasses;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -52,7 +50,7 @@
 import org.robolectric.shadows.ShadowLooper;
 
 @RunWith(FrameworkRobolectricTestRunner.class)
-@Config(manifest = Config.NONE, sdk = 26)
+@Config(manifest = Config.NONE, sdk = 26, shadows = {ShadowEventLog.class})
 @SystemLoaderClasses({TransportManager.class, TransportClient.class})
 @Presubmit
 public class TransportClientTest {
@@ -64,6 +62,7 @@
     @Mock private IBackupTransport.Stub mIBackupTransport;
     private TransportClient mTransportClient;
     private ComponentName mTransportComponent;
+    private String mTransportString;
     private Intent mBindIntent;
     private ShadowLooper mShadowLooper;
 
@@ -75,14 +74,11 @@
         mShadowLooper = shadowOf(mainLooper);
         mTransportComponent =
                 new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport");
+        mTransportString = mTransportComponent.flattenToShortString();
         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),
@@ -169,7 +165,7 @@
     }
 
     @Test
-    public void testConnectAsync_whenFrameworkDoesntBind_releasesConnection() throws Exception {
+    public void testConnectAsync_whenFrameworkDoesNotBind_releasesConnection() throws Exception {
         when(mContext.bindServiceAsUser(
                         eq(mBindIntent),
                         any(ServiceConnection.class),
@@ -213,32 +209,110 @@
                 .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));
+    }
+
+    @Test
+    public void testConnectAsync_beforeFrameworkCall_logsBoundTransition() {
+        ShadowEventLog.clearEvents();
+
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+
+        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 1);
+    }
+
+    @Test
+    public void testConnectAsync_afterOnServiceConnected_logsBoundAndConnectedTransitions() {
+        ShadowEventLog.clearEvents();
+
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+
+        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 1);
+        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_CONNECTION, mTransportString, 1);
+    }
+
+    @Test
+    public void testConnectAsync_afterOnBindingDied_logsBoundAndUnboundTransitions() {
+        ShadowEventLog.clearEvents();
+
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onBindingDied(mTransportComponent);
+
+        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 1);
+        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 0);
+    }
+
+    @Test
+    public void testUnbind_whenConnected_logsDisconnectedAndUnboundTransitions() {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+        ShadowEventLog.clearEvents();
+
+        mTransportClient.unbind("caller1");
+
+        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_CONNECTION, mTransportString, 0);
+        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 0);
+    }
+
+    @Test
+    public void testOnServiceDisconnected_whenConnected_logsDisconnectedAndUnboundTransitions() {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+        ShadowEventLog.clearEvents();
+
+        connection.onServiceDisconnected(mTransportComponent);
+
+        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_CONNECTION, mTransportString, 0);
+        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 0);
+    }
+
+    @Test
+    public void testOnBindingDied_whenConnected_logsDisconnectedAndUnboundTransitions() {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+        ShadowEventLog.clearEvents();
+
+        connection.onBindingDied(mTransportComponent);
+
+        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_CONNECTION, mTransportString, 0);
+        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 0);
+    }
+
+    private void assertEventLogged(int tag, Object... values) {
+        assertThat(ShadowEventLog.hasEvent(tag, values)).isTrue();
+    }
 
     private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) {
         ArgumentCaptor<ServiceConnection> connectionCaptor =
diff --git a/services/robotests/src/com/android/server/testing/ShadowEventLog.java b/services/robotests/src/com/android/server/testing/ShadowEventLog.java
new file mode 100644
index 0000000..b8059f4
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/ShadowEventLog.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import android.util.EventLog;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+@Implements(EventLog.class)
+public class ShadowEventLog {
+    private final static LinkedHashSet<Entry> ENTRIES = new LinkedHashSet<>();
+
+    @Implementation
+    public static int writeEvent(int tag, Object... values) {
+        ENTRIES.add(new Entry(tag, Arrays.asList(values)));
+        // Currently we don't care about the return value, if we do, estimate it correctly
+        return 0;
+    }
+
+    public static boolean hasEvent(int tag, Object... values) {
+        return ENTRIES.contains(new Entry(tag, Arrays.asList(values)));
+    }
+
+    public static void clearEvents() {
+        ENTRIES.clear();
+    }
+
+    public static class Entry {
+        public final int tag;
+        public final List<Object> values;
+
+        public Entry(int tag, List<Object> values) {
+            this.tag = tag;
+            this.values = values;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Entry entry = (Entry) o;
+            return tag == entry.tag && values.equals(entry.values);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = tag;
+            result = 31 * result + values.hashCode();
+            return result;
+        }
+    }
+}
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..9923fa8 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -22,6 +22,7 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING;
 import static com.android.server.am.ActivityStack.ActivityState.PAUSING;
 import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
@@ -35,24 +36,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 +110,28 @@
 
     @Test
     public void testPausingWhenVisibleFromStopped() throws Exception {
+        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;
+
         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());
+        assertTrue(pauseFound.value);
 
-        boolean pauseFound = false;
+        // Make sure that the state does not change for current non-stopping states.
+        mActivity.state = INITIALIZING;
 
-        for (ClientTransaction targetTransaction : transaction.getAllValues()) {
-            if (targetTransaction.getLifecycleStateRequest() instanceof PauseActivityItem) {
-                pauseFound = true;
-            }
-        }
+        mActivity.makeVisibleIfNeeded(null /* starting */);
 
-        assertTrue(pauseFound);
+        assertEquals(mActivity.state, INITIALIZING);
     }
 
     @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 f5894e0..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,6 +15,7 @@
  */
 package com.android.server.devicepolicy;
 
+import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
 import android.app.IActivityManager;
 import android.app.NotificationManager;
@@ -188,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 57030e4..1df0ff2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -87,6 +87,7 @@
 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;
@@ -4020,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,
@@ -4069,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),
@@ -4482,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 11e32f8..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,6 +25,7 @@
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
+import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
 import android.app.IActivityManager;
 import android.app.NotificationManager;
@@ -84,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;
@@ -120,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 288a8be..dec962e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -91,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) {
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/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index ccf2aaf..272b5d8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -27,6 +27,7 @@
 import android.app.IActivityManager;
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.trust.TrustManager;
 import android.content.ComponentName;
 import android.content.pm.UserInfo;
@@ -41,6 +42,7 @@
 
 import com.android.internal.widget.ILockSettings;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.server.LocalServices;
 
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
@@ -75,6 +77,7 @@
     FakeStorageManager mStorageManager;
     IActivityManager mActivityManager;
     DevicePolicyManager mDevicePolicyManager;
+    DevicePolicyManagerInternal mDevicePolicyManagerInternal;
     KeyStore mKeyStore;
     MockSyntheticPasswordManager mSpManager;
 
@@ -88,6 +91,10 @@
         mStorageManager = new FakeStorageManager();
         mActivityManager = mock(IActivityManager.class);
         mDevicePolicyManager = mock(DevicePolicyManager.class);
+        mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class);
+
+        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+        LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal);
 
         mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
                 mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class));
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java
new file mode 100644
index 0000000..4ad9f19
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java
@@ -0,0 +1,116 @@
+/*
+ * 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.locksettings;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.server.testutils.TestUtils.assertExpectException;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+import android.os.RemoteException;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
+
+/**
+ * Run the synthetic password tests with caching enabled.
+ *
+ * By default, those tests run without caching. Untrusted credential reset depends on caching so
+ * this class included those tests.
+ */
+public class CachedSyntheticPasswordTests extends SyntheticPasswordTests {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        enableSpCaching(true);
+    }
+
+    private void enableSpCaching(boolean enable) {
+        when(mDevicePolicyManagerInternal
+                .canUserHaveUntrustedCredentialReset(anyInt())).thenReturn(enable);
+    }
+
+    public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException {
+        final String PASSWORD = "testSyntheticPasswordClearCredential-password";
+        final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
+
+        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+        long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+        // clear password
+        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
+                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
+        assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+
+        // set a new password
+        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                    .getResponseCode());
+        assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+    }
+
+    public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException {
+        final String PASSWORD = "testSyntheticPasswordClearCredential-password";
+        final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
+
+        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+        long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+        // Untrusted change password
+        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+        assertNotEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+        assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+
+        // Verify the password
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                    .getResponseCode());
+    }
+
+    public void testUntrustedCredentialChangeBlockedIfSpNotCached() throws RemoteException {
+        final String PASSWORD = "testUntrustedCredentialChangeBlockedIfSpNotCached-password";
+        final String NEWPASSWORD = "testUntrustedCredentialChangeBlockedIfSpNotCached-newpassword";
+
+        // Disable caching for this test
+        enableSpCaching(false);
+
+        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+        long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+        // Untrusted change password
+        assertExpectException(IllegalStateException.class, /* messageRegex= */ null,
+                () -> mService.setLockCredential(
+                        NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                        null, PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID));
+        assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+
+        // Verify the new password doesn't work but the old one still does
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(
+                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                        .getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                        .getResponseCode());
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 2e4c74f..b07d6ac 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -112,7 +112,7 @@
                 mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
-    private void initializeCredentialUnderSP(String password, int userId) throws RemoteException {
+    protected void initializeCredentialUnderSP(String password, int userId) throws RemoteException {
         enableSyntheticPassword();
         int quality = password != null ? PASSWORD_QUALITY_ALPHABETIC
                 : PASSWORD_QUALITY_UNSPECIFIED;
@@ -129,7 +129,6 @@
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD,
                 PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
-        mGateKeeperService.clearSecureUserId(PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
                         .getResponseCode());
@@ -170,44 +169,6 @@
         assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
     }
 
-    public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException {
-        final String PASSWORD = "testSyntheticPasswordClearCredential-password";
-        final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
-
-        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
-        long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
-        // clear password
-        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
-                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
-        assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
-
-        // set a new password
-        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
-                    .getResponseCode());
-        assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
-    }
-
-    public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException {
-        final String PASSWORD = "testSyntheticPasswordClearCredential-password";
-        final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
-
-        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
-        long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
-        // Untrusted change password
-        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
-        assertNotEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
-        assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
-
-        // Verify the password
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
-                    .getResponseCode());
-    }
-
     public void testManagedProfileUnifiedChallengeMigration() throws RemoteException {
         final String UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd";
         disableSyntheticPassword();
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 7538079..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
@@ -40,7 +40,7 @@
 import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyDerivationParameters;
+import android.security.keystore.KeyDerivationParams;
 import android.security.keystore.EntryRecoveryData;
 import android.security.keystore.RecoveryData;
 import android.support.test.InstrumentationRegistry;
@@ -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(
@@ -283,13 +284,13 @@
         mKeySyncTask.run();
 
         RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
-        KeyDerivationParameters keyDerivationParameters =
-                recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParameters();
-        assertThat(keyDerivationParameters.getAlgorithm()).isEqualTo(
-                KeyDerivationParameters.ALGORITHM_SHA256);
+        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();
@@ -299,8 +300,8 @@
                 /*vaultParams=*/ KeySyncUtils.packVaultParams(
                         mKeyPair.getPublic(),
                         counterId,
-                        /*maxAttempts=*/ 10,
-                        TEST_DEVICE_ID));
+                        TEST_DEVICE_ID,
+                        /*maxAttempts=*/ 10));
         List<EntryRecoveryData> applicationKeys = recoveryData.getEntryRecoveryData();
         assertThat(applicationKeys).hasSize(1);
         EntryRecoveryData keyData = applicationKeys.get(0);
@@ -469,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 0eddcf3..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
@@ -42,7 +42,7 @@
 import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyDerivationParameters;
+import android.security.keystore.KeyDerivationParams;
 import android.security.keystore.EntryRecoveryData;
 import android.security.keystore.RecoveryMetadata;
 import android.security.keystore.RecoveryManager;
@@ -254,7 +254,7 @@
                         new RecoveryMetadata(
                                 TYPE_LOCKSCREEN,
                                 TYPE_PASSWORD,
-                                KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                                KeyDerivationParams.createSha256Params(TEST_SALT),
                                 TEST_SECRET)));
 
         verify(mMockContext, times(1))
@@ -273,7 +273,7 @@
                         new RecoveryMetadata(
                                 TYPE_LOCKSCREEN,
                                 TYPE_PASSWORD,
-                                KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                                KeyDerivationParams.createSha256Params(TEST_SALT),
                                 TEST_SECRET)));
 
         assertEquals(1, mRecoverySessionStorage.size());
@@ -311,7 +311,7 @@
                             new RecoveryMetadata(
                                     TYPE_LOCKSCREEN,
                                     TYPE_PASSWORD,
-                                    KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                                    KeyDerivationParams.createSha256Params(TEST_SALT),
                                     TEST_SECRET)));
             fail("should have thrown");
         } catch (ServiceSpecificException e) {
@@ -333,7 +333,7 @@
                             new RecoveryMetadata(
                                     TYPE_LOCKSCREEN,
                                     TYPE_PASSWORD,
-                                    KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                                    KeyDerivationParams.createSha256Params(TEST_SALT),
                                     TEST_SECRET)));
             fail("should have thrown");
         } catch (ServiceSpecificException e) {
@@ -366,7 +366,7 @@
                 ImmutableList.of(new RecoveryMetadata(
                         TYPE_LOCKSCREEN,
                         TYPE_PASSWORD,
-                        KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+                        KeyDerivationParams.createSha256Params(TEST_SALT),
                         TEST_SECRET)));
 
         try {
@@ -390,7 +390,7 @@
                 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();
@@ -422,7 +422,7 @@
                 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();
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 6d5e740..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
@@ -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();
@@ -328,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();
     }
 
@@ -437,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);
     }
@@ -479,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
@@ -590,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/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
index 9a6da0e..b6c370e 100644
--- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
@@ -16,15 +16,16 @@
 
 package com.android.server.policy;
 
-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;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
@@ -34,7 +35,6 @@
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.view.Surface;
 import android.view.WindowManager;
 
 import org.junit.Before;
@@ -128,7 +128,23 @@
     }
 
     @Test
-    public void layoutWindowLw_withDisplayCutout_fullscreen() {
+    public void layoutWindowLw_withhDisplayCutout_never() {
+        addDisplayCutout();
+
+        mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
+        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);
+        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+    }
+
+    @Test
+    public void layoutWindowLw_withDisplayCutout_layoutFullscreen() {
         addDisplayCutout();
 
         mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
@@ -137,6 +153,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,8 +179,8 @@
     public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() {
         addDisplayCutout();
 
-        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-        mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
+        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
+        mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         mPolicy.addWindow(mAppWindow);
 
         mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -217,7 +249,7 @@
         setRotation(ROTATION_90);
 
         mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-        mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
+        mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         mPolicy.addWindow(mAppWindow);
 
         mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
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/wm/DragDropControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
index ce76a22..ac29163 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java
@@ -16,27 +16,38 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.content.ClipData;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManagerInternal;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.InputChannel;
 import android.view.Surface;
 import android.view.SurfaceSession;
+import android.view.View;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+
 /**
  * Tests for the {@link DragDropController} class.
  *
@@ -46,36 +57,92 @@
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 public class DragDropControllerTests extends WindowTestsBase {
-    private static final int TIMEOUT_MS = 1000;
-    private DragDropController mTarget;
+    private static final int TIMEOUT_MS = 3000;
+    private TestDragDropController mTarget;
     private WindowState mWindow;
     private IBinder mToken;
 
+    static class TestDragDropController extends DragDropController {
+        @GuardedBy("sWm.mWindowMap")
+        private Runnable mCloseCallback;
+
+        TestDragDropController(WindowManagerService service, Looper looper) {
+            super(service, looper);
+        }
+
+        void setOnClosedCallbackLocked(Runnable runnable) {
+            assertTrue(dragDropActiveLocked());
+            mCloseCallback = runnable;
+        }
+
+        @Override
+        void onDragStateClosedLocked(DragState dragState) {
+            super.onDragStateClosedLocked(dragState);
+            if (mCloseCallback != null) {
+                mCloseCallback.run();
+                mCloseCallback = null;
+            }
+        }
+    }
+
+    /**
+     * Creates a window state which can be used as a drop target.
+     */
+    private WindowState createDropTargetWindow(String name, int ownerId) {
+        final WindowTestUtils.TestAppWindowToken token = new WindowTestUtils.TestAppWindowToken(
+                mDisplayContent);
+        final TaskStack stack = createStackControllerOnStackOnDisplay(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer;
+        final Task task = createTaskInStack(stack, ownerId);
+        task.addChild(token, 0);
+
+        final WindowState window = createWindow(
+                null, TYPE_BASE_APPLICATION, token, name, ownerId, false);
+        window.mInputChannel = new InputChannel();
+        window.mHasSurface = true;
+        return window;
+    }
+
     @Before
     public void setUp() throws Exception {
+        final UserManagerInternal userManager = mock(UserManagerInternal.class);
+        LocalServices.addService(UserManagerInternal.class, userManager);
+
         super.setUp();
-        assertNotNull(sWm.mDragDropController);
-        mTarget = sWm.mDragDropController;
-        mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
+
+        mTarget = new TestDragDropController(sWm, sWm.mH.getLooper());
+        mDisplayContent = spy(mDisplayContent);
+        mWindow = createDropTargetWindow("Drag test window", 0);
+        when(mDisplayContent.getTouchableWinAtPointLocked(0, 0)).thenReturn(mWindow);
+        when(sWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true);
+
         synchronized (sWm.mWindowMap) {
-            // Because sWm is a static object, the previous operation may remain.
-            assertFalse(mTarget.dragDropActiveLocked());
+            sWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
         }
     }
 
     @After
-    public void tearDown() {
-        if (mToken != null) {
-            mTarget.cancelDragAndDrop(mToken);
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        final CountDownLatch latch;
+        synchronized (sWm.mWindowMap) {
+            if (!mTarget.dragDropActiveLocked()) {
+                return;
+            }
+            if (mToken != null) {
+                mTarget.cancelDragAndDrop(mToken);
+            }
+            latch = new CountDownLatch(1);
+            mTarget.setOnClosedCallbackLocked(() -> {
+                latch.countDown();
+            });
         }
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     @Test
-    public void testPrepareDrag() throws Exception {
-        final Surface surface = new Surface();
-        mToken = mTarget.prepareDrag(
-                new SurfaceSession(), 0, 0, mWindow.mClient, 0, 100, 100, surface);
-        assertNotNull(mToken);
+    public void testDragFlow() throws Exception {
+        dragFlow(0, ClipData.newPlainText("label", "Test"), 0, 0);
     }
 
     @Test
@@ -85,4 +152,33 @@
                 new SurfaceSession(), 0, 0, mWindow.mClient, 0, 0, 0, surface);
         assertNull(mToken);
     }
+
+    @Test
+    public void testPerformDrag_NullDataWithGrantUri() throws Exception {
+        dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0);
+    }
+
+    @Test
+    public void testPerformDrag_NullDataToOtherUser() throws Exception {
+        final WindowState otherUsersWindow =
+                createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE);
+        when(mDisplayContent.getTouchableWinAtPointLocked(10, 10))
+                .thenReturn(otherUsersWindow);
+
+        dragFlow(0, null, 10, 10);
+    }
+
+    private void dragFlow(int flag, ClipData data, float dropX, float dropY) {
+        final Surface surface = new Surface();
+        mToken = mTarget.prepareDrag(
+                new SurfaceSession(), 0, 0, mWindow.mClient, flag, 100, 100, surface);
+        assertNotNull(mToken);
+
+        assertTrue(sWm.mInputManager.transferTouchFocus(null, null));
+        assertTrue(mTarget.performDrag(
+                mWindow.mClient, mToken, 0, 0, 0, 0, 0, data));
+
+        mTarget.handleMotionEvent(false, dropX, dropY);
+        mToken = mWindow.mClient.asBinder();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java
new file mode 100644
index 0000000..897be34
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Postsubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * atest FrameworksServicesTests:com.android.server.wm.RemoteAnimationControllerTest
+ */
+@SmallTest
+@FlakyTest(detail = "Promote to presubmit if non-flakyness is established")
+@RunWith(AndroidJUnit4.class)
+public class RemoteAnimationControllerTest extends WindowTestsBase {
+
+    @Mock SurfaceControl mMockLeash;
+    @Mock Transaction mMockTransaction;
+    @Mock OnAnimationFinishedCallback mFinishedCallback;
+    @Mock IRemoteAnimationRunner mMockRunner;
+    private RemoteAnimationAdapter mAdapter;
+    private RemoteAnimationController mController;
+    private final OffsettableClock mClock = new OffsettableClock.Stopped();
+    private TestHandler mHandler;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50);
+        sWm.mH.runWithScissors(() -> {
+            mHandler = new TestHandler(null, mClock);
+        }, 0);
+        mController = new RemoteAnimationController(sWm, mAdapter, mHandler);
+    }
+
+    @Test
+    public void testRun() throws Exception {
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        sWm.mOpeningApps.add(win.mAppToken);
+        try {
+            final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
+                    new Point(50, 100), new Rect(50, 100, 150, 150));
+            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            mController.goodToGo();
+
+            final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                    ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+            assertEquals(1, appsCaptor.getValue().length);
+            final RemoteAnimationTarget app = appsCaptor.getValue()[0];
+            assertEquals(new Point(50, 100), app.position);
+            assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds);
+            assertEquals(win.mAppToken.getPrefixOrderIndex(), app.prefixOrderIndex);
+            assertEquals(win.mAppToken.getTask().mTaskId, app.taskId);
+            assertEquals(mMockLeash, app.leash);
+            assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect);
+            assertEquals(false, app.isTranslucent);
+            verify(mMockTransaction).setLayer(mMockLeash, app.prefixOrderIndex);
+            verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y);
+            verify(mMockTransaction).setWindowCrop(mMockLeash, new Rect(0, 0, 100, 50));
+
+            finishedCaptor.getValue().onAnimationFinished();
+            verify(mFinishedCallback).onAnimationFinished(eq(adapter));
+        } finally {
+            sWm.mOpeningApps.clear();
+        }
+    }
+
+    @Test
+    public void testCancel() throws Exception {
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150));
+        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+        mController.goodToGo();
+
+        adapter.onAnimationCancelled(mMockLeash);
+        verify(mMockRunner).onAnimationCancelled();
+    }
+
+    @Test
+    public void testTimeout() throws Exception {
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
+                new Point(50, 100), new Rect(50, 100, 150, 150));
+        adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+        mController.goodToGo();
+
+        mClock.fastForward(2500);
+        mHandler.timeAdvance();
+
+        verify(mMockRunner).onAnimationCancelled();
+        verify(mFinishedCallback).onAnimationFinished(eq(adapter));
+    }
+}
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/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
index 7be203a..6a4710b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
@@ -223,6 +223,19 @@
         assertFalse(app.canAffectSystemUiFlags());
     }
 
+    @Test
+    public void testIsSelfOrAncestorWindowAnimating() throws Exception {
+        final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
+        final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1");
+        final WindowState child2 = createWindow(child1, FIRST_SUB_WINDOW, "child2");
+        assertFalse(child2.isSelfOrAncestorWindowAnimatingExit());
+        child2.mAnimatingExit = true;
+        assertTrue(child2.isSelfOrAncestorWindowAnimatingExit());
+        child2.mAnimatingExit = false;
+        root.mAnimatingExit = true;
+        assertTrue(child2.isSelfOrAncestorWindowAnimatingExit());
+    }
+
     private void testPrepareWindowToDisplayDuringRelayout(boolean wasVisible) {
         final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
         root.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index c699a94..69b1378 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -230,20 +230,22 @@
             boolean ownerCanAddInternalSystemWindow) {
         final WindowToken token = createWindowToken(
                 dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
-        return createWindow(parent, type, token, name, ownerCanAddInternalSystemWindow);
+        return createWindow(parent, type, token, name, 0 /* ownerId */,
+                ownerCanAddInternalSystemWindow);
     }
 
     static WindowState createWindow(WindowState parent, int type, WindowToken token, String name) {
-        return createWindow(parent, type, token, name, false /* ownerCanAddInternalSystemWindow */);
+        return createWindow(parent, type, token, name, 0 /* ownerId */,
+                false /* ownerCanAddInternalSystemWindow */);
     }
 
     static WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
-            boolean ownerCanAddInternalSystemWindow) {
+            int ownerId, boolean ownerCanAddInternalSystemWindow) {
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
         attrs.setTitle(name);
 
         final WindowState w = new WindowState(sWm, sMockSession, sIWindow, token, parent, OP_NONE,
-                0, attrs, VISIBLE, 0, ownerCanAddInternalSystemWindow);
+                0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow);
         // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
         // adding it to the token...
         token.addWindow(w);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
new file mode 100644
index 0000000..689c2ce
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.notification;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.server.UiServiceTestCase;
+import com.android.server.notification.NotificationManagerService.NotificationAssistants;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class NotificationAssistantsTest extends UiServiceTestCase {
+
+    @Mock
+    private PackageManager mPm;
+    @Mock
+    private IPackageManager miPm;
+    @Mock
+    private UserManager mUm;
+    @Mock
+    NotificationManagerService mNm;
+
+    NotificationAssistants mAssistants;
+
+    @Mock
+    private ManagedServices.UserProfiles mUserProfiles;
+
+    Object mLock = new Object();
+
+    UserInfo mZero = new UserInfo(0, "zero", 0);
+    UserInfo mTen = new UserInfo(10, "ten", 0);
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        getContext().setMockPackageManager(mPm);
+        getContext().addMockSystemService(Context.USER_SERVICE, mUm);
+        mAssistants = spy(mNm.new NotificationAssistants(getContext(), mLock, mUserProfiles, miPm));
+
+        List<ResolveInfo> approved = new ArrayList<>();
+        ResolveInfo resolve = new ResolveInfo();
+        approved.add(resolve);
+        ServiceInfo info = new ServiceInfo();
+        info.packageName = "a";
+        info.name="a";
+        resolve.serviceInfo = info;
+        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(approved);
+
+        List<UserInfo> users = new ArrayList<>();
+        users.add(mZero);
+        users.add(mTen);
+        users.add(new UserInfo(11, "11", 0));
+        users.add(new UserInfo(12, "12", 0));
+        for (UserInfo user : users) {
+            when(mUm.getUserInfo(eq(user.id))).thenReturn(user);
+        }
+        when(mUm.getUsers()).thenReturn(users);
+        when(mUm.getUsers(anyBoolean())).thenReturn(users);
+        when(mUserProfiles.getCurrentProfileIds()).thenReturn(new int[] {0, 10, 11, 12});
+    }
+
+    @Test
+    public void testXmlUpgrade() throws Exception {
+        String xml = "<enabled_assistants/>";
+
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(xml.toString().getBytes())), null);
+        parser.nextTag();
+        mAssistants.readXml(parser);
+
+        //once per user
+        verify(mNm, times(mUm.getUsers().size())).readDefaultAssistant(anyInt());
+    }
+
+    @Test
+    public void testXmlUpgradeExistingApprovedComponents() throws Exception {
+        String xml = "<enabled_assistants>"
+                + "<service_listing approved=\"b/b\" user=\"10\" primary=\"true\" />"
+                + "</enabled_assistants>";
+
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(xml.toString().getBytes())), null);
+        parser.nextTag();
+        mAssistants.readXml(parser);
+
+        // once per user
+        verify(mNm, times(mUm.getUsers().size())).readDefaultAssistant(anyInt());
+        verify(mAssistants, times(1)).addApprovedList(
+                new ComponentName("b", "b").flattenToString(),10, true);
+    }
+
+    @Test
+    public void testXmlUpgradeOnce() throws Exception {
+        String xml = "<enabled_assistants/>";
+
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(xml.toString().getBytes())), null);
+        parser.nextTag();
+        mAssistants.readXml(parser);
+
+        XmlSerializer serializer = new FastXmlSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+        mAssistants.writeXml(serializer, true);
+        serializer.endDocument();
+        serializer.flush();
+
+        //once per user
+        verify(mNm, times(mUm.getUsers().size())).readDefaultAssistant(anyInt());
+
+        Mockito.reset(mNm);
+
+        parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), null);
+        parser.nextTag();
+        mAssistants.readXml(parser);
+
+        //once per user
+        verify(mNm, never()).readDefaultAssistant(anyInt());
+    }
+}
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/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 5a1a3e3..ce0b551 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -960,8 +960,9 @@
     public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool";
 
     /**
-     * String to identify carrier name in CarrierConfig app. This string is used only if
-     * #KEY_CARRIER_NAME_OVERRIDE_BOOL is true
+     * String to identify carrier name in CarrierConfig app. This string overrides SPN if
+     * #KEY_CARRIER_NAME_OVERRIDE_BOOL is true; otherwise, it will be used if its value is provided
+     * and SPN is unavailable
      * @hide
      */
     public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f411ef7..8edc8b1 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;
@@ -2514,6 +2515,33 @@
         }
     }
 
+    /**
+     * Resets the Carrier Keys in the database. This involves 2 steps:
+     *  1. Delete the keys from the database.
+     *  2. Send an intent to download new Certificates.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * @hide
+     */
+    public void resetCarrierKeysForImsiEncryption() {
+        try {
+            IPhoneSubInfo info = getSubscriberInfo();
+            if (info == null) {
+                throw new RuntimeException("IMSI error: Subscriber Info is null");
+            }
+            int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId());
+            info.resetCarrierKeysForImsiEncryption(subId, mContext.getOpPackageName());
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "getCarrierInfoForImsiEncryption RemoteException" + ex);
+            throw new RuntimeException("IMSI error: Remote Exception");
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            Rlog.e(TAG, "getCarrierInfoForImsiEncryption NullPointerException" + ex);
+            throw new RuntimeException("IMSI error: Null Pointer exception");
+        }
+    }
+
    /**
      * @param keyAvailability bitmask that defines the availabilty of keys for a type.
      * @param keyType the key type which is being checked. (WLAN, EPDG)
@@ -2549,7 +2577,7 @@
      * device keystore.
      * <p>
      * Requires Permission:
-     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      * @param imsiEncryptionInfo which includes the Key Type, the Public Key
      *        (java.security.PublicKey) and the Key Identifier.and the Key Identifier.
      *        The keyIdentifier Attribute value pair that helps a server locate
@@ -4932,6 +4960,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
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/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/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index 0f31821..f8a040d 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -152,6 +152,13 @@
     in ImsiEncryptionInfo imsiEncryptionInfo);
 
     /**
+     * Resets the Carrier Keys in the database. This involves 2 steps:
+     *  1. Delete the keys from the database.
+     *  2. Send an intent to download new Certificates.
+     */
+    void resetCarrierKeysForImsiEncryption(int subId, String callingPackage);
+
+    /**
      * Retrieves the alpha identifier associated with the voice mail number.
      */
     String getVoiceMailAlphaTag(String callingPackage);
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/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index f29d993c..51369d0 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -486,4 +486,10 @@
     */
     public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
             "com.android.omadm.service.CONFIGURATION_UPDATE";
+
+    /**
+     * Broadcast action to trigger the Carrier Certificate download.
+     */
+    public static final String ACTION_CARRIER_CERTIFICATE_DOWNLOAD =
+            "com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD";
 }
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-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/keystore/KeyDerivationParameters.aidl b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.aidl
similarity index 74%
copy from core/java/android/security/keystore/KeyDerivationParameters.aidl
copy to tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.aidl
index 92316e5..b43d49a 100644
--- a/core/java/android/security/keystore/KeyDerivationParameters.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.keystore;
+package com.android.frameworks.perftests.am.util;
 
-/* @hide */
-parcelable KeyDerivationParameters;
+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/IpSecConfigTest.java b/tests/net/java/android/net/IpSecConfigTest.java
index efc01f2a..f6c5532 100644
--- a/tests/net/java/android/net/IpSecConfigTest.java
+++ b/tests/net/java/android/net/IpSecConfigTest.java
@@ -36,19 +36,16 @@
     public void testDefaults() throws Exception {
         IpSecConfig c = new IpSecConfig();
         assertEquals(IpSecTransform.MODE_TRANSPORT, c.getMode());
-        assertEquals("", c.getLocalAddress());
-        assertEquals("", c.getRemoteAddress());
+        assertEquals("", c.getSourceAddress());
+        assertEquals("", c.getDestinationAddress());
         assertNull(c.getNetwork());
         assertEquals(IpSecTransform.ENCAP_NONE, c.getEncapType());
         assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getEncapSocketResourceId());
         assertEquals(0, c.getEncapRemotePort());
         assertEquals(0, c.getNattKeepaliveInterval());
-        for (int direction :
-                new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN}) {
-            assertNull(c.getEncryption(direction));
-            assertNull(c.getAuthentication(direction));
-            assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getSpiResourceId(direction));
-        }
+        assertNull(c.getEncryption());
+        assertNull(c.getAuthentication());
+        assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getSpiResourceId());
     }
 
     @Test
@@ -57,34 +54,21 @@
 
         IpSecConfig c = new IpSecConfig();
         c.setMode(IpSecTransform.MODE_TUNNEL);
-        c.setLocalAddress("0.0.0.0");
-        c.setRemoteAddress("1.2.3.4");
+        c.setSourceAddress("0.0.0.0");
+        c.setDestinationAddress("1.2.3.4");
         c.setEncapType(android.system.OsConstants.UDP_ENCAP_ESPINUDP);
         c.setEncapSocketResourceId(7);
         c.setEncapRemotePort(22);
         c.setNattKeepaliveInterval(42);
         c.setEncryption(
-                IpSecTransform.DIRECTION_OUT,
                 new IpSecAlgorithm(
                         IpSecAlgorithm.CRYPT_AES_CBC,
                         new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}));
         c.setAuthentication(
-                IpSecTransform.DIRECTION_OUT,
                 new IpSecAlgorithm(
                         IpSecAlgorithm.AUTH_HMAC_MD5,
                         new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0}));
-        c.setSpiResourceId(IpSecTransform.DIRECTION_OUT, 1984);
-        c.setEncryption(
-                IpSecTransform.DIRECTION_IN,
-                new IpSecAlgorithm(
-                        IpSecAlgorithm.CRYPT_AES_CBC,
-                        new byte[] {2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}));
-        c.setAuthentication(
-                IpSecTransform.DIRECTION_IN,
-                new IpSecAlgorithm(
-                        IpSecAlgorithm.AUTH_HMAC_MD5,
-                        new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 1}));
-        c.setSpiResourceId(IpSecTransform.DIRECTION_IN, 99);
+        c.setSpiResourceId(1984);
         assertParcelingIsLossless(c);
     }
 
diff --git a/tests/net/java/android/net/IpSecManagerTest.java b/tests/net/java/android/net/IpSecManagerTest.java
index 0f40b45..cc3366f 100644
--- a/tests/net/java/android/net/IpSecManagerTest.java
+++ b/tests/net/java/android/net/IpSecManagerTest.java
@@ -81,15 +81,13 @@
         IpSecSpiResponse spiResp =
                 new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, DROID_SPI);
         when(mMockIpSecService.allocateSecurityParameterIndex(
-                        eq(IpSecTransform.DIRECTION_IN),
                         eq(GOOGLE_DNS_4.getHostAddress()),
                         eq(DROID_SPI),
                         anyObject()))
                 .thenReturn(spiResp);
 
         IpSecManager.SecurityParameterIndex droidSpi =
-                mIpSecManager.allocateSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_IN, GOOGLE_DNS_4, DROID_SPI);
+                mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4, DROID_SPI);
         assertEquals(DROID_SPI, droidSpi.getSpi());
 
         droidSpi.close();
@@ -103,15 +101,13 @@
         IpSecSpiResponse spiResp =
                 new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, DROID_SPI);
         when(mMockIpSecService.allocateSecurityParameterIndex(
-                        eq(IpSecTransform.DIRECTION_OUT),
                         eq(GOOGLE_DNS_4.getHostAddress()),
                         eq(IpSecManager.INVALID_SECURITY_PARAMETER_INDEX),
                         anyObject()))
                 .thenReturn(spiResp);
 
         IpSecManager.SecurityParameterIndex randomSpi =
-                mIpSecManager.allocateSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4);
+                mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4);
 
         assertEquals(DROID_SPI, randomSpi.getSpi());
 
@@ -124,16 +120,15 @@
      * Throws resource unavailable exception
      */
     @Test
-    public void testAllocSpiResUnavaiableExeption() throws Exception {
+    public void testAllocSpiResUnavailableException() throws Exception {
         IpSecSpiResponse spiResp =
                 new IpSecSpiResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE, 0, 0);
         when(mMockIpSecService.allocateSecurityParameterIndex(
-                        anyInt(), anyString(), anyInt(), anyObject()))
+                        anyString(), anyInt(), anyObject()))
                 .thenReturn(spiResp);
 
         try {
-            mIpSecManager.allocateSecurityParameterIndex(
-                    IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4);
+            mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4);
             fail("ResourceUnavailableException was not thrown");
         } catch (IpSecManager.ResourceUnavailableException e) {
         }
@@ -143,15 +138,14 @@
      * Throws spi unavailable exception
      */
     @Test
-    public void testAllocSpiSpiUnavaiableExeption() throws Exception {
+    public void testAllocSpiSpiUnavailableException() throws Exception {
         IpSecSpiResponse spiResp = new IpSecSpiResponse(IpSecManager.Status.SPI_UNAVAILABLE, 0, 0);
         when(mMockIpSecService.allocateSecurityParameterIndex(
-                        anyInt(), anyString(), anyInt(), anyObject()))
+                        anyString(), anyInt(), anyObject()))
                 .thenReturn(spiResp);
 
         try {
-            mIpSecManager.allocateSecurityParameterIndex(
-                    IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4);
+            mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4);
             fail("ResourceUnavailableException was not thrown");
         } catch (IpSecManager.ResourceUnavailableException e) {
         }
@@ -163,8 +157,7 @@
     @Test
     public void testRequestAllocInvalidSpi() throws Exception {
         try {
-            mIpSecManager.allocateSecurityParameterIndex(
-                    IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4, 0);
+            mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4, 0);
             fail("Able to allocate invalid spi");
         } catch (IllegalArgumentException e) {
         }
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/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 2282c13..1ddab5b 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -32,7 +32,6 @@
 import android.net.IpSecConfig;
 import android.net.IpSecManager;
 import android.net.IpSecSpiResponse;
-import android.net.IpSecTransform;
 import android.net.IpSecTransformResponse;
 import android.net.NetworkUtils;
 import android.os.Binder;
@@ -54,14 +53,14 @@
 @RunWith(Parameterized.class)
 public class IpSecServiceParameterizedTest {
 
-    private static final int TEST_SPI_OUT = 0xD1201D;
-    private static final int TEST_SPI_IN = TEST_SPI_OUT + 1;
+    private static final int TEST_SPI = 0xD1201D;
 
-    private final String mRemoteAddr;
+    private final String mDestinationAddr;
+    private final String mSourceAddr;
 
     @Parameterized.Parameters
     public static Collection ipSecConfigs() {
-        return Arrays.asList(new Object[][] {{"8.8.4.4"}, {"2601::10"}});
+        return Arrays.asList(new Object[][] {{"1.2.3.4", "8.8.4.4"}, {"2601::2", "2601::10"}});
     }
 
     private static final byte[] AEAD_KEY = {
@@ -96,11 +95,9 @@
     private static final IpSecAlgorithm AEAD_ALGO =
             new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
 
-    private static final int[] DIRECTIONS =
-            new int[] {IpSecTransform.DIRECTION_IN, IpSecTransform.DIRECTION_OUT};
-
-    public IpSecServiceParameterizedTest(String remoteAddr) {
-        mRemoteAddr = remoteAddr;
+    public IpSecServiceParameterizedTest(String sourceAddr, String destAddr) {
+        mSourceAddr = sourceAddr;
+        mDestinationAddr = destAddr;
     }
 
     @Before
@@ -116,44 +113,30 @@
 
     @Test
     public void testIpSecServiceReserveSpi() throws Exception {
-        when(mMockNetd.ipSecAllocateSpi(
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        eq(mRemoteAddr),
-                        eq(TEST_SPI_OUT)))
-                .thenReturn(TEST_SPI_OUT);
+        when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI)))
+                .thenReturn(TEST_SPI);
 
         IpSecSpiResponse spiResp =
                 mIpSecService.allocateSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder());
+                        mDestinationAddr, TEST_SPI, new Binder());
         assertEquals(IpSecManager.Status.OK, spiResp.status);
-        assertEquals(TEST_SPI_OUT, spiResp.spi);
+        assertEquals(TEST_SPI, spiResp.spi);
     }
 
     @Test
     public void testReleaseSecurityParameterIndex() throws Exception {
-        when(mMockNetd.ipSecAllocateSpi(
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        eq(mRemoteAddr),
-                        eq(TEST_SPI_OUT)))
-                .thenReturn(TEST_SPI_OUT);
+        when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI)))
+                .thenReturn(TEST_SPI);
 
         IpSecSpiResponse spiResp =
                 mIpSecService.allocateSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder());
+                        mDestinationAddr, TEST_SPI, new Binder());
 
         mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId);
 
         verify(mMockNetd)
                 .ipSecDeleteSecurityAssociation(
-                        eq(spiResp.resourceId),
-                        anyInt(),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_OUT));
+                        eq(spiResp.resourceId), anyString(), anyString(), eq(TEST_SPI));
 
         // Verify quota and RefcountedResource objects cleaned up
         IpSecService.UserRecord userRecord =
@@ -169,17 +152,12 @@
 
     @Test
     public void testSecurityParameterIndexBinderDeath() throws Exception {
-        when(mMockNetd.ipSecAllocateSpi(
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        eq(mRemoteAddr),
-                        eq(TEST_SPI_OUT)))
-                .thenReturn(TEST_SPI_OUT);
+        when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI)))
+                .thenReturn(TEST_SPI);
 
         IpSecSpiResponse spiResp =
                 mIpSecService.allocateSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder());
+                        mDestinationAddr, TEST_SPI, new Binder());
 
         IpSecService.UserRecord userRecord =
                 mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
@@ -190,11 +168,7 @@
 
         verify(mMockNetd)
                 .ipSecDeleteSecurityAssociation(
-                        eq(spiResp.resourceId),
-                        anyInt(),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_OUT));
+                        eq(spiResp.resourceId), anyString(), anyString(), eq(TEST_SPI));
 
         // Verify quota and RefcountedResource objects cleaned up
         assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent);
@@ -206,14 +180,12 @@
         }
     }
 
-    private int getNewSpiResourceId(int direction, String remoteAddress, int returnSpi)
-            throws Exception {
-        when(mMockNetd.ipSecAllocateSpi(anyInt(), anyInt(), anyString(), anyString(), anyInt()))
+    private int getNewSpiResourceId(String remoteAddress, int returnSpi) throws Exception {
+        when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), anyString(), anyInt()))
                 .thenReturn(returnSpi);
 
         IpSecSpiResponse spi =
                 mIpSecService.allocateSecurityParameterIndex(
-                        direction,
                         NetworkUtils.numericToInetAddress(remoteAddress).getHostAddress(),
                         IpSecManager.INVALID_SECURITY_PARAMETER_INDEX,
                         new Binder());
@@ -221,20 +193,14 @@
     }
 
     private void addDefaultSpisAndRemoteAddrToIpSecConfig(IpSecConfig config) throws Exception {
-        config.setSpiResourceId(
-                IpSecTransform.DIRECTION_OUT,
-                getNewSpiResourceId(IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT));
-        config.setSpiResourceId(
-                IpSecTransform.DIRECTION_IN,
-                getNewSpiResourceId(IpSecTransform.DIRECTION_IN, mRemoteAddr, TEST_SPI_IN));
-        config.setRemoteAddress(mRemoteAddr);
+        config.setSpiResourceId(getNewSpiResourceId(mDestinationAddr, TEST_SPI));
+        config.setSourceAddress(mSourceAddr);
+        config.setDestinationAddress(mDestinationAddr);
     }
 
     private void addAuthAndCryptToIpSecConfig(IpSecConfig config) throws Exception {
-        for (int direction : DIRECTIONS) {
-            config.setEncryption(direction, CRYPT_ALGO);
-            config.setAuthentication(direction, AUTH_ALGO);
-        }
+        config.setEncryption(CRYPT_ALGO);
+        config.setAuthentication(AUTH_ALGO);
     }
 
     @Test
@@ -251,32 +217,10 @@
                 .ipSecAddSecurityAssociation(
                         eq(createTransformResp.resourceId),
                         anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
                         anyString(),
                         anyString(),
                         anyLong(),
-                        eq(TEST_SPI_OUT),
-                        eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
-                        eq(AUTH_KEY),
-                        anyInt(),
-                        eq(IpSecAlgorithm.CRYPT_AES_CBC),
-                        eq(CRYPT_KEY),
-                        anyInt(),
-                        eq(""),
-                        eq(new byte[] {}),
-                        eq(0),
-                        anyInt(),
-                        anyInt(),
-                        anyInt());
-        verify(mMockNetd)
-                .ipSecAddSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_IN),
-                        anyString(),
-                        anyString(),
-                        anyLong(),
-                        eq(TEST_SPI_IN),
+                        eq(TEST_SPI),
                         eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
                         eq(AUTH_KEY),
                         anyInt(),
@@ -296,8 +240,7 @@
         IpSecConfig ipSecConfig = new IpSecConfig();
         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
 
-        ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_OUT, AEAD_ALGO);
-        ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
+        ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO);
 
         IpSecTransformResponse createTransformResp =
                 mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
@@ -307,32 +250,10 @@
                 .ipSecAddSecurityAssociation(
                         eq(createTransformResp.resourceId),
                         anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
                         anyString(),
                         anyString(),
                         anyLong(),
-                        eq(TEST_SPI_OUT),
-                        eq(""),
-                        eq(new byte[] {}),
-                        eq(0),
-                        eq(""),
-                        eq(new byte[] {}),
-                        eq(0),
-                        eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM),
-                        eq(AEAD_KEY),
-                        anyInt(),
-                        anyInt(),
-                        anyInt(),
-                        anyInt());
-        verify(mMockNetd)
-                .ipSecAddSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_IN),
-                        anyString(),
-                        anyString(),
-                        anyLong(),
-                        eq(TEST_SPI_IN),
+                        eq(TEST_SPI),
                         eq(""),
                         eq(new byte[] {}),
                         eq(0),
@@ -359,18 +280,7 @@
 
         verify(mMockNetd)
                 .ipSecDeleteSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_OUT));
-        verify(mMockNetd)
-                .ipSecDeleteSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        eq(IpSecTransform.DIRECTION_IN),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_IN));
+                        eq(createTransformResp.resourceId), anyString(), anyString(), eq(TEST_SPI));
 
         // Verify quota and RefcountedResource objects cleaned up
         IpSecService.UserRecord userRecord =
@@ -404,18 +314,7 @@
 
         verify(mMockNetd)
                 .ipSecDeleteSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_OUT));
-        verify(mMockNetd)
-                .ipSecDeleteSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        eq(IpSecTransform.DIRECTION_IN),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_IN));
+                        eq(createTransformResp.resourceId), anyString(), anyString(), eq(TEST_SPI));
 
         // Verify quota and RefcountedResource objects cleaned up
         assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent);
@@ -439,30 +338,22 @@
         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
 
         int resourceId = createTransformResp.resourceId;
-        mIpSecService.applyTransportModeTransform(pfd, resourceId);
+        mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId);
 
         verify(mMockNetd)
                 .ipSecApplyTransportModeTransform(
                         eq(pfd.getFileDescriptor()),
                         eq(resourceId),
-                        eq(IpSecTransform.DIRECTION_OUT),
+                        eq(IpSecManager.DIRECTION_OUT),
                         anyString(),
                         anyString(),
-                        eq(TEST_SPI_OUT));
-        verify(mMockNetd)
-                .ipSecApplyTransportModeTransform(
-                        eq(pfd.getFileDescriptor()),
-                        eq(resourceId),
-                        eq(IpSecTransform.DIRECTION_IN),
-                        anyString(),
-                        anyString(),
-                        eq(TEST_SPI_IN));
+                        eq(TEST_SPI));
     }
 
     @Test
     public void testRemoveTransportModeTransform() throws Exception {
         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
-        mIpSecService.removeTransportModeTransform(pfd, 1);
+        mIpSecService.removeTransportModeTransforms(pfd, 1);
 
         verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor());
     }
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index 0467989..b2a27e8 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -105,9 +105,6 @@
     private static final IpSecAlgorithm AEAD_ALGO =
             new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
 
-    private static final int[] DIRECTIONS =
-            new int[] {IpSecTransform.DIRECTION_IN, IpSecTransform.DIRECTION_OUT};
-
     static {
         try {
             INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
@@ -303,83 +300,75 @@
 
     @Test
     public void testValidateAlgorithmsAuth() {
-        for (int direction : DIRECTIONS) {
-            // Validate that correct algorithm type succeeds
-            IpSecConfig config = new IpSecConfig();
-            config.setAuthentication(direction, AUTH_ALGO);
-            mIpSecService.validateAlgorithms(config, direction);
+        // Validate that correct algorithm type succeeds
+        IpSecConfig config = new IpSecConfig();
+        config.setAuthentication(AUTH_ALGO);
+        mIpSecService.validateAlgorithms(config);
 
-            // Validate that incorrect algorithm types fails
-            for (IpSecAlgorithm algo : new IpSecAlgorithm[] {CRYPT_ALGO, AEAD_ALGO}) {
-                try {
-                    config = new IpSecConfig();
-                    config.setAuthentication(direction, algo);
-                    mIpSecService.validateAlgorithms(config, direction);
-                    fail("Did not throw exception on invalid algorithm type");
-                } catch (IllegalArgumentException expected) {
-                }
+        // Validate that incorrect algorithm types fails
+        for (IpSecAlgorithm algo : new IpSecAlgorithm[] {CRYPT_ALGO, AEAD_ALGO}) {
+            try {
+                config = new IpSecConfig();
+                config.setAuthentication(algo);
+                mIpSecService.validateAlgorithms(config);
+                fail("Did not throw exception on invalid algorithm type");
+            } catch (IllegalArgumentException expected) {
             }
         }
     }
 
     @Test
     public void testValidateAlgorithmsCrypt() {
-        for (int direction : DIRECTIONS) {
-            // Validate that correct algorithm type succeeds
-            IpSecConfig config = new IpSecConfig();
-            config.setEncryption(direction, CRYPT_ALGO);
-            mIpSecService.validateAlgorithms(config, direction);
+        // Validate that correct algorithm type succeeds
+        IpSecConfig config = new IpSecConfig();
+        config.setEncryption(CRYPT_ALGO);
+        mIpSecService.validateAlgorithms(config);
 
-            // Validate that incorrect algorithm types fails
-            for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, AEAD_ALGO}) {
-                try {
-                    config = new IpSecConfig();
-                    config.setEncryption(direction, algo);
-                    mIpSecService.validateAlgorithms(config, direction);
-                    fail("Did not throw exception on invalid algorithm type");
-                } catch (IllegalArgumentException expected) {
-                }
+        // Validate that incorrect algorithm types fails
+        for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, AEAD_ALGO}) {
+            try {
+                config = new IpSecConfig();
+                config.setEncryption(algo);
+                mIpSecService.validateAlgorithms(config);
+                fail("Did not throw exception on invalid algorithm type");
+            } catch (IllegalArgumentException expected) {
             }
         }
     }
 
     @Test
     public void testValidateAlgorithmsAead() {
-        for (int direction : DIRECTIONS) {
-            // Validate that correct algorithm type succeeds
-            IpSecConfig config = new IpSecConfig();
-            config.setAuthenticatedEncryption(direction, AEAD_ALGO);
-            mIpSecService.validateAlgorithms(config, direction);
+        // Validate that correct algorithm type succeeds
+        IpSecConfig config = new IpSecConfig();
+        config.setAuthenticatedEncryption(AEAD_ALGO);
+        mIpSecService.validateAlgorithms(config);
 
-            // Validate that incorrect algorithm types fails
-            for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, CRYPT_ALGO}) {
-                try {
-                    config = new IpSecConfig();
-                    config.setAuthenticatedEncryption(direction, algo);
-                    mIpSecService.validateAlgorithms(config, direction);
-                    fail("Did not throw exception on invalid algorithm type");
-                } catch (IllegalArgumentException expected) {
-                }
+        // Validate that incorrect algorithm types fails
+        for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, CRYPT_ALGO}) {
+            try {
+                config = new IpSecConfig();
+                config.setAuthenticatedEncryption(algo);
+                mIpSecService.validateAlgorithms(config);
+                fail("Did not throw exception on invalid algorithm type");
+            } catch (IllegalArgumentException expected) {
             }
         }
     }
 
     @Test
     public void testValidateAlgorithmsAuthCrypt() {
-        for (int direction : DIRECTIONS) {
-            // Validate that correct algorithm type succeeds
-            IpSecConfig config = new IpSecConfig();
-            config.setAuthentication(direction, AUTH_ALGO);
-            config.setEncryption(direction, CRYPT_ALGO);
-            mIpSecService.validateAlgorithms(config, direction);
-        }
+        // Validate that correct algorithm type succeeds
+        IpSecConfig config = new IpSecConfig();
+        config.setAuthentication(AUTH_ALGO);
+        config.setEncryption(CRYPT_ALGO);
+        mIpSecService.validateAlgorithms(config);
     }
 
     @Test
     public void testValidateAlgorithmsNoAlgorithms() {
         IpSecConfig config = new IpSecConfig();
         try {
-            mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN);
+            mIpSecService.validateAlgorithms(config);
             fail("Expected exception; no algorithms specified");
         } catch (IllegalArgumentException expected) {
         }
@@ -388,10 +377,10 @@
     @Test
     public void testValidateAlgorithmsAeadWithAuth() {
         IpSecConfig config = new IpSecConfig();
-        config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
-        config.setAuthentication(IpSecTransform.DIRECTION_IN, AUTH_ALGO);
+        config.setAuthenticatedEncryption(AEAD_ALGO);
+        config.setAuthentication(AUTH_ALGO);
         try {
-            mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN);
+            mIpSecService.validateAlgorithms(config);
             fail("Expected exception; both AEAD and auth algorithm specified");
         } catch (IllegalArgumentException expected) {
         }
@@ -400,10 +389,10 @@
     @Test
     public void testValidateAlgorithmsAeadWithCrypt() {
         IpSecConfig config = new IpSecConfig();
-        config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
-        config.setEncryption(IpSecTransform.DIRECTION_IN, CRYPT_ALGO);
+        config.setAuthenticatedEncryption(AEAD_ALGO);
+        config.setEncryption(CRYPT_ALGO);
         try {
-            mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN);
+            mIpSecService.validateAlgorithms(config);
             fail("Expected exception; both AEAD and crypt algorithm specified");
         } catch (IllegalArgumentException expected) {
         }
@@ -412,11 +401,11 @@
     @Test
     public void testValidateAlgorithmsAeadWithAuthAndCrypt() {
         IpSecConfig config = new IpSecConfig();
-        config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
-        config.setAuthentication(IpSecTransform.DIRECTION_IN, AUTH_ALGO);
-        config.setEncryption(IpSecTransform.DIRECTION_IN, CRYPT_ALGO);
+        config.setAuthenticatedEncryption(AEAD_ALGO);
+        config.setAuthentication(AUTH_ALGO);
+        config.setEncryption(CRYPT_ALGO);
         try {
-            mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN);
+            mIpSecService.validateAlgorithms(config);
             fail("Expected exception; AEAD, auth and crypt algorithm specified");
         } catch (IllegalArgumentException expected) {
         }
@@ -434,7 +423,7 @@
     @Test
     public void testRemoveTransportModeTransform() throws Exception {
         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
-        mIpSecService.removeTransportModeTransform(pfd, 1);
+        mIpSecService.removeTransportModeTransforms(pfd, 1);
 
         verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor());
     }
@@ -447,7 +436,7 @@
             try {
                 IpSecSpiResponse spiResp =
                         mIpSecService.allocateSecurityParameterIndex(
-                                IpSecTransform.DIRECTION_OUT, address, DROID_SPI, new Binder());
+                                address, DROID_SPI, new Binder());
                 fail("Invalid address was passed through IpSecService validation: " + address);
             } catch (IllegalArgumentException e) {
             } catch (Exception e) {
@@ -519,7 +508,6 @@
         // tracks the resource ID.
         when(mMockNetd.ipSecAllocateSpi(
                         anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
                         anyString(),
                         eq(InetAddress.getLoopbackAddress().getHostAddress()),
                         anyInt()))
@@ -528,7 +516,6 @@
         for (int i = 0; i < MAX_NUM_SPIS; i++) {
             IpSecSpiResponse newSpi =
                     mIpSecService.allocateSecurityParameterIndex(
-                            0x1,
                             InetAddress.getLoopbackAddress().getHostAddress(),
                             DROID_SPI + i,
                             new Binder());
@@ -544,7 +531,6 @@
         // Try to reserve one more SPI, and should fail.
         IpSecSpiResponse extraSpi =
                 mIpSecService.allocateSecurityParameterIndex(
-                        0x1,
                         InetAddress.getLoopbackAddress().getHostAddress(),
                         DROID_SPI + MAX_NUM_SPIS,
                         new Binder());
@@ -558,7 +544,6 @@
         // Should successfully reserve one more spi.
         extraSpi =
                 mIpSecService.allocateSecurityParameterIndex(
-                        0x1,
                         InetAddress.getLoopbackAddress().getHostAddress(),
                         DROID_SPI + MAX_NUM_SPIS,
                         new Binder());
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/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()
 {