Merge "Use EuiccManager API to disable current carrier." into oc-dr1-dev
diff --git a/Android.mk b/Android.mk
index 00dc784..933ac62 100644
--- a/Android.mk
+++ b/Android.mk
@@ -357,6 +357,7 @@
 	core/java/android/view/IPinnedStackController.aidl \
 	core/java/android/view/IPinnedStackListener.aidl \
 	core/java/android/view/IRotationWatcher.aidl \
+	core/java/android/view/IWallpaperVisibilityListener.aidl \
 	core/java/android/view/IWindow.aidl \
 	core/java/android/view/IWindowFocusObserver.aidl \
 	core/java/android/view/IWindowId.aidl \
@@ -772,14 +773,9 @@
 	frameworks/base/core/java/android/view/textservice/SuggestionsInfo.aidl \
 	frameworks/base/core/java/android/service/carrier/CarrierIdentifier.aidl \
 	frameworks/base/core/java/android/service/carrier/MessagePdu.aidl \
-	frameworks/base/core/java/android/service/euicc/DeleteResult.aidl \
-	frameworks/base/core/java/android/service/euicc/DownloadResult.aidl \
-	frameworks/base/core/java/android/service/euicc/EraseResult.aidl \
 	frameworks/base/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.aidl \
 	frameworks/base/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.aidl \
 	frameworks/base/core/java/android/service/euicc/GetEuiccProfileInfoListResult.aidl \
-	frameworks/base/core/java/android/service/euicc/SwitchResult.aidl \
-	frameworks/base/core/java/android/service/euicc/UpdateNicknameResult.aidl \
 	frameworks/base/core/java/android/service/notification/Adjustment.aidl \
 	frameworks/base/core/java/android/service/notification/Condition.aidl \
 	frameworks/base/core/java/android/service/notification/SnoozeCriterion.aidl \
diff --git a/api/current.txt b/api/current.txt
index 5dd3839..d7f8ecd 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6089,13 +6089,17 @@
 
   public final class WallpaperColors implements android.os.Parcelable {
     ctor public WallpaperColors(android.os.Parcel);
-    ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>);
-    ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean);
+    ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int);
     method public int describeContents();
-    method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors();
-    method public boolean supportsDarkText();
+    method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap);
+    method public static android.app.WallpaperColors fromDrawable(android.graphics.drawable.Drawable);
+    method public int getColorHints();
+    method public android.graphics.Color getPrimaryColor();
+    method public android.graphics.Color getSecondaryColor();
+    method public android.graphics.Color getTertiaryColor();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR;
+    field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1
   }
 
   public final class WallpaperInfo implements android.os.Parcelable {
@@ -37581,12 +37585,10 @@
     method public int getDesiredMinimumHeight();
     method public int getDesiredMinimumWidth();
     method public android.view.SurfaceHolder getSurfaceHolder();
-    method public void invalidateColors();
     method public boolean isPreview();
     method public boolean isVisible();
     method public void onApplyWindowInsets(android.view.WindowInsets);
     method public android.os.Bundle onCommand(java.lang.String, int, int, int, android.os.Bundle, boolean);
-    method public android.app.WallpaperColors onComputeWallpaperColors();
     method public void onCreate(android.view.SurfaceHolder);
     method public void onDesiredSizeChanged(int, int);
     method public void onDestroy();
diff --git a/api/system-current.txt b/api/system-current.txt
index dfad7a2..85e9175 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6299,13 +6299,17 @@
 
   public final class WallpaperColors implements android.os.Parcelable {
     ctor public WallpaperColors(android.os.Parcel);
-    ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>);
-    ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean);
+    ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int);
     method public int describeContents();
-    method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors();
-    method public boolean supportsDarkText();
+    method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap);
+    method public static android.app.WallpaperColors fromDrawable(android.graphics.drawable.Drawable);
+    method public int getColorHints();
+    method public android.graphics.Color getPrimaryColor();
+    method public android.graphics.Color getSecondaryColor();
+    method public android.graphics.Color getTertiaryColor();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR;
+    field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1
   }
 
   public final class WallpaperInfo implements android.os.Parcelable {
@@ -40793,12 +40797,10 @@
     method public int getDesiredMinimumHeight();
     method public int getDesiredMinimumWidth();
     method public android.view.SurfaceHolder getSurfaceHolder();
-    method public void invalidateColors();
     method public boolean isPreview();
     method public boolean isVisible();
     method public void onApplyWindowInsets(android.view.WindowInsets);
     method public android.os.Bundle onCommand(java.lang.String, int, int, int, android.os.Bundle, boolean);
-    method public android.app.WallpaperColors onComputeWallpaperColors();
     method public void onCreate(android.view.SurfaceHolder);
     method public void onDesiredSizeChanged(int, int);
     method public void onDestroy();
diff --git a/api/test-current.txt b/api/test-current.txt
index 9bafd5f..ed9071a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6110,13 +6110,17 @@
 
   public final class WallpaperColors implements android.os.Parcelable {
     ctor public WallpaperColors(android.os.Parcel);
-    ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>);
-    ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean);
+    ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int);
     method public int describeContents();
-    method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors();
-    method public boolean supportsDarkText();
+    method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap);
+    method public static android.app.WallpaperColors fromDrawable(android.graphics.drawable.Drawable);
+    method public int getColorHints();
+    method public android.graphics.Color getPrimaryColor();
+    method public android.graphics.Color getSecondaryColor();
+    method public android.graphics.Color getTertiaryColor();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR;
+    field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1
   }
 
   public final class WallpaperInfo implements android.os.Parcelable {
@@ -37788,12 +37792,10 @@
     method public int getDesiredMinimumHeight();
     method public int getDesiredMinimumWidth();
     method public android.view.SurfaceHolder getSurfaceHolder();
-    method public void invalidateColors();
     method public boolean isPreview();
     method public boolean isVisible();
     method public void onApplyWindowInsets(android.view.WindowInsets);
     method public android.os.Bundle onCommand(java.lang.String, int, int, int, android.os.Bundle, boolean);
-    method public android.app.WallpaperColors onComputeWallpaperColors();
     method public void onCreate(android.view.SurfaceHolder);
     method public void onDesiredSizeChanged(int, int);
     method public void onDestroy();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 3574f8d..24c144c 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -719,7 +719,7 @@
     public static final int FINISH_TASK_WITH_ACTIVITY = 2;
 
     static final String FRAGMENTS_TAG = "android:fragments";
-    private static final String LAST_ACCESSIBILITY_ID = "android:lastAccessibilityId";
+    private static final String LAST_AUTOFILL_ID = "android:lastAutofillId";
 
     private static final String AUTOFILL_RESET_NEEDED = "@android:autofillResetNeeded";
     private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState";
@@ -853,8 +853,8 @@
 
     private boolean mAutoFillResetNeeded;
 
-    /** The last accessibility id that was returned from {@link #getNextAccessibilityId()} */
-    private int mLastAccessibilityId = View.LAST_APP_ACCESSIBILITY_ID;
+    /** The last autofill id that was returned from {@link #getNextAutofillId()} */
+    private int mLastAutofillId = View.LAST_APP_AUTOFILL_ID;
 
     private AutofillPopupWindow mAutofillPopupWindow;
 
@@ -999,7 +999,7 @@
         }
         if (savedInstanceState != null) {
             mAutoFillResetNeeded = savedInstanceState.getBoolean(AUTOFILL_RESET_NEEDED, false);
-            mLastAccessibilityId = savedInstanceState.getInt(LAST_ACCESSIBILITY_ID, View.NO_ID);
+            mLastAutofillId = savedInstanceState.getInt(LAST_AUTOFILL_ID, View.NO_ID);
 
             if (mAutoFillResetNeeded) {
                 getAutofillManager().onCreate(savedInstanceState);
@@ -1348,24 +1348,23 @@
     }
 
     /**
-     * Gets the next accessibility ID.
+     * Gets the next autofill ID.
      *
-     * <p>All IDs will be bigger than {@link View#LAST_APP_ACCESSIBILITY_ID}. All IDs returned
+     * <p>All IDs will be bigger than {@link View#LAST_APP_AUTOFILL_ID}. All IDs returned
      * will be unique.
      *
      * @return A ID that is unique in the activity
      *
      * {@hide}
      */
-    @Override
-    public int getNextAccessibilityId() {
-        if (mLastAccessibilityId == Integer.MAX_VALUE - 1) {
-            mLastAccessibilityId = View.LAST_APP_ACCESSIBILITY_ID;
+    public int getNextAutofillId() {
+        if (mLastAutofillId == Integer.MAX_VALUE - 1) {
+            mLastAutofillId = View.LAST_APP_AUTOFILL_ID;
         }
 
-        mLastAccessibilityId++;
+        mLastAutofillId++;
 
-        return mLastAccessibilityId;
+        return mLastAutofillId;
     }
 
     /**
@@ -1563,7 +1562,7 @@
     protected void onSaveInstanceState(Bundle outState) {
         outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
 
-        outState.putInt(LAST_ACCESSIBILITY_ID, mLastAccessibilityId);
+        outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
         Parcelable p = mFragments.saveAllState();
         if (p != null) {
             outState.putParcelable(FRAGMENTS_TAG, p);
@@ -7455,7 +7454,7 @@
 
     /** @hide */
     @Override
-    @NonNull public View[] findViewsByAccessibilityIdTraversal(@NonNull int[] viewIds) {
+    @NonNull public View[] findViewsByAutofillIdTraversal(@NonNull int[] viewIds) {
         final View[] views = new View[viewIds.length];
         final ArrayList<ViewRootImpl> roots =
                 WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
@@ -7466,7 +7465,7 @@
             if (rootView != null) {
                 for (int viewNum = 0; viewNum < viewIds.length; viewNum++) {
                     if (views[viewNum] == null) {
-                        views[viewNum] = rootView.findViewByAccessibilityIdTraversal(
+                        views[viewNum] = rootView.findViewByAutofillIdTraversal(
                                 viewIds[viewNum]);
                     }
                 }
@@ -7478,14 +7477,14 @@
 
     /** @hide */
     @Override
-    @Nullable public View findViewByAccessibilityIdTraversal(int viewId) {
+    @Nullable public View findViewByAutofillIdTraversal(int viewId) {
         final ArrayList<ViewRootImpl> roots =
                 WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
         for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
             final View rootView = roots.get(rootNum).getView();
 
             if (rootView != null) {
-                final View view = rootView.findViewByAccessibilityIdTraversal(viewId);
+                final View view = rootView.findViewByAutofillIdTraversal(viewId);
                 if (view != null) {
                     return view;
                 }
@@ -7499,7 +7498,7 @@
     @Override
     @NonNull public boolean[] getViewVisibility(@NonNull int[] viewIds) {
         final boolean[] isVisible = new boolean[viewIds.length];
-        final View views[] = findViewsByAccessibilityIdTraversal(viewIds);
+        final View views[] = findViewsByAutofillIdTraversal(viewIds);
 
         for (int i = 0; i < viewIds.length; i++) {
             View view = views[i];
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index d3b4b40..e5fe240 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -24,6 +24,7 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.SystemClock;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.SparseIntArray;
 
@@ -134,8 +135,10 @@
      *
      * @param reasons A map from stack id to a reason integer why the transition was started,, which
      *                must be one of the APP_TRANSITION_* values.
+     * @param timestamp The time at which the app transition started in
+     *                  {@link SystemClock#uptimeMillis()} timebase.
      */
-    public abstract void notifyAppTransitionStarting(SparseIntArray reasons);
+    public abstract void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp);
 
     /**
      * Callback for window manager to let activity manager know that the app transition was
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index cbb93a0..6dead3e 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1244,7 +1244,7 @@
                 // Once we parcel the thumbnail for transfering over to the system, create a copy of
                 // the bitmap to a hardware bitmap and pass through the GraphicBuffer
                 if (mThumbnail != null) {
-                    final Bitmap hwBitmap = mThumbnail.copy(Config.HARDWARE, true /* immutable */);
+                    final Bitmap hwBitmap = mThumbnail.copy(Config.HARDWARE, false /* isMutable */);
                     if (hwBitmap != null) {
                         b.putParcelable(KEY_ANIM_THUMBNAIL, hwBitmap.createGraphicBufferHandle());
                     } else {
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index b3c70a4..8f172ba 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -16,63 +16,206 @@
 
 package android.app;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Size;
 
-import android.util.Pair;
+import com.android.internal.graphics.palette.Palette;
+import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
- * A class containing information about the colors of a wallpaper.
+ * Provides information about the colors of a wallpaper.
+ * <p>
+ * This class contains two main components:
+ * <ul>
+ * <li>Named colors: Most visually representative colors of a wallpaper. Can be either
+ * {@link WallpaperColors#getPrimaryColor()}, {@link WallpaperColors#getSecondaryColor()}
+ * or {@link WallpaperColors#getTertiaryColor()}.
+ * </li>
+ * <li>Hints: How colors may affect other system components. Currently the only supported hint is
+ * {@link WallpaperColors#HINT_SUPPORTS_DARK_TEXT}, which specifies if dark text is preferred
+ * over the wallpaper.</li>
+ * </ul>
  */
 public final class WallpaperColors implements Parcelable {
 
-    private static final float BRIGHT_LUMINANCE = 0.9f;
-    private final List<Pair<Color, Integer>> mColors;
-    private final boolean mSupportsDarkText;
+    /**
+     * Specifies that dark text is preferred over the current wallpaper for best presentation.
+     * <p>
+     * eg. A launcher may set its text color to black if this flag is specified.
+     */
+    public static final int HINT_SUPPORTS_DARK_TEXT = 0x1;
+
+    // Maximum size that a bitmap can have to keep our calculations sane
+    private static final int MAX_BITMAP_SIZE = 112;
+
+    // Even though we have a maximum size, we'll mainly match bitmap sizes
+    // using the area instead. This way our comparisons are aspect ratio independent.
+    private static final int MAX_WALLPAPER_EXTRACTION_AREA = MAX_BITMAP_SIZE * MAX_BITMAP_SIZE;
+
+    // When extracting the main colors, only consider colors
+    // present in at least MIN_COLOR_OCCURRENCE of the image
+    private static final float MIN_COLOR_OCCURRENCE = 0.05f;
+
+    // Minimum mean luminosity that an image needs to have to support dark text
+    private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = 0.9f;
+    // We also check if the image has dark pixels in it,
+    // to avoid bright images with some dark spots.
+    private static final float DARK_PIXEL_LUMINANCE = 0.45f;
+    private static final float MAX_DARK_AREA = 0.05f;
+
+    private final ArrayList<Color> mMainColors;
+    private int mColorHints;
 
     public WallpaperColors(Parcel parcel) {
-        mColors = new ArrayList<>();
-        int count = parcel.readInt();
-        for (int i=0; i < count; i++) {
-            Color color = Color.valueOf(parcel.readInt());
-            int weight = parcel.readInt();
-            mColors.add(new Pair<>(color, weight));
+        mMainColors = new ArrayList<>();
+        final int count = parcel.readInt();
+        for (int i = 0; i < count; i++) {
+            final int colorInt = parcel.readInt();
+            Color color = Color.valueOf(colorInt);
+            mMainColors.add(color);
         }
-        mSupportsDarkText = parcel.readBoolean();
+        mColorHints = parcel.readInt();
     }
 
     /**
-     * Wallpaper color details containing a list of colors and their weights,
-     * as if it were an histogram.
-     * This list can be extracted from a bitmap by the Palette API.
+     * Constructs {@link WallpaperColors} from a drawable.
+     * <p>
+     * Main colors will be extracted from the drawable and hints will be calculated.
      *
-     * Dark text support will be calculated internally based on the histogram.
-     *
-     * @param colors list of pairs where each pair contains a color
-     *               and number of occurrences/influence.
+     * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT
+     * @param drawable Source where to extract from.
      */
-    public WallpaperColors(List<Pair<Color, Integer>> colors) {
-        this(colors, calculateDarkTextSupport(colors));
+    public static WallpaperColors fromDrawable(Drawable drawable) {
+        int width = drawable.getIntrinsicWidth();
+        int height = drawable.getIntrinsicHeight();
+
+        // Some drawables do not have intrinsic dimensions
+        if (width <= 0 || height <= 0) {
+            width = MAX_BITMAP_SIZE;
+            height = MAX_BITMAP_SIZE;
+        }
+
+        Size optimalSize = calculateOptimalSize(width, height);
+        Bitmap bitmap = Bitmap.createBitmap(optimalSize.getWidth(), optimalSize.getHeight(),
+                Bitmap.Config.ARGB_8888);
+        final Canvas bmpCanvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        drawable.draw(bmpCanvas);
+
+        final WallpaperColors colors = WallpaperColors.fromBitmap(bitmap);
+        bitmap.recycle();
+
+        return colors;
     }
 
     /**
-     * Wallpaper color details containing a list of colors and their weights,
-     * as if it were an histogram.
-     * Explicit dark text support.
+     * Constructs {@link WallpaperColors} from a bitmap.
+     * <p>
+     * Main colors will be extracted from the bitmap and hints will be calculated.
      *
-     * @param colors list of pairs where each pair contains a color
-     *               and number of occurrences/influence.
-     * @param supportsDarkText can have dark text on top or not
+     * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT
+     * @param bitmap Source where to extract from.
      */
-    public WallpaperColors(List<Pair<Color, Integer>> colors, boolean supportsDarkText) {
-        if (colors == null)
-            colors = new ArrayList<>();
-        mColors = colors;
-        mSupportsDarkText = supportsDarkText;
+    public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap) {
+        if (bitmap == null) {
+            throw new IllegalArgumentException("Bitmap can't be null");
+        }
+
+        final int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
+        if (bitmapArea > MAX_WALLPAPER_EXTRACTION_AREA) {
+            Size optimalSize = calculateOptimalSize(bitmap.getWidth(), bitmap.getHeight());
+            Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, optimalSize.getWidth(),
+                    optimalSize.getHeight(), true /* filter */);
+            bitmap.recycle();
+            bitmap = scaledBitmap;
+        }
+
+        final Palette palette = Palette
+                .from(bitmap)
+                .setQuantizer(new VariationalKMeansQuantizer())
+                .maximumColorCount(5)
+                .clearFilters()
+                .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
+                .generate();
+
+        // Remove insignificant colors and sort swatches by population
+        final ArrayList<Palette.Swatch> swatches = new ArrayList<>(palette.getSwatches());
+        final float minColorArea = bitmap.getWidth() * bitmap.getHeight() * MIN_COLOR_OCCURRENCE;
+        swatches.removeIf(s -> s.getPopulation() < minColorArea);
+        swatches.sort((a, b) -> b.getPopulation() - a.getPopulation());
+
+        final int swatchesSize = swatches.size();
+        Color primary = null, secondary = null, tertiary = null;
+
+        swatchLoop:
+        for (int i = 0; i < swatchesSize; i++) {
+            Color color = Color.valueOf(swatches.get(i).getRgb());
+            switch (i) {
+                case 0:
+                    primary = color;
+                    break;
+                case 1:
+                    secondary = color;
+                    break;
+                case 2:
+                    tertiary = color;
+                    break;
+                default:
+                    // out of bounds
+                    break swatchLoop;
+            }
+        }
+
+        int hints = 0;
+        if (calculateDarkTextSupport(bitmap)) {
+            hints |= HINT_SUPPORTS_DARK_TEXT;
+        }
+        return new WallpaperColors(primary, secondary, tertiary, hints);
+    }
+
+    /**
+     * Constructs a new object from three colors, where hints can be specified.
+     *
+     * @param primaryColor Primary color.
+     * @param secondaryColor Secondary color.
+     * @param tertiaryColor Tertiary color.
+     * @param colorHints A combination of WallpaperColor hints.
+     * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT
+     * @see WallpaperColors#fromBitmap(Bitmap)
+     * @see WallpaperColors#fromDrawable(Drawable)
+     */
+    public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor,
+            @Nullable Color tertiaryColor, int colorHints) {
+
+        if (primaryColor == null) {
+            throw new IllegalArgumentException("Primary color should never be null.");
+        }
+
+        mMainColors = new ArrayList<>(3);
+        mMainColors.add(primaryColor);
+        if (secondaryColor != null) {
+            mMainColors.add(secondaryColor);
+        }
+        if (tertiaryColor != null) {
+            if (secondaryColor == null) {
+                throw new IllegalArgumentException("tertiaryColor can't be specified when "
+                        + "secondaryColor is null");
+            }
+            mMainColors.add(tertiaryColor);
+        }
+
+        mColorHints = colorHints;
     }
 
     public static final Creator<WallpaperColors> CREATOR = new Creator<WallpaperColors>() {
@@ -94,21 +237,53 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        int count = mColors.size();
+        List<Color> mainColors = getMainColors();
+        int count = mainColors.size();
         dest.writeInt(count);
-        for (Pair<Color, Integer> color : mColors) {
-            dest.writeInt(color.first.toArgb());
-            dest.writeInt(color.second);
+        for (int i = 0; i < count; i++) {
+            Color color = mainColors.get(i);
+            dest.writeInt(color.toArgb());
         }
-        dest.writeBoolean(mSupportsDarkText);
+        dest.writeInt(mColorHints);
     }
 
     /**
-     * List of colors with their occurrences. The bigger the int, the more relevant the color.
-     * @return list of colors paired with their weights.
+     * Gets the most visually representative color of the wallpaper.
+     * "Visually representative" means easily noticeable in the image,
+     * probably happening at high frequency.
+     *
+     * @return A color.
      */
-    public List<Pair<Color, Integer>> getColors() {
-        return mColors;
+    public @NonNull Color getPrimaryColor() {
+        return mMainColors.get(0);
+    }
+
+    /**
+     * Gets the second most preeminent color of the wallpaper. Can be null.
+     *
+     * @return A color, may be null.
+     */
+    public @Nullable Color getSecondaryColor() {
+        return mMainColors.size() < 2 ? null : mMainColors.get(1);
+    }
+
+    /**
+     * Gets the third most preeminent color of the wallpaper. Can be null.
+     *
+     * @return A color, may be null.
+     */
+    public @Nullable Color getTertiaryColor() {
+        return mMainColors.size() < 3 ? null : mMainColors.get(2);
+    }
+
+    /**
+     * List of most preeminent colors, sorted by importance.
+     *
+     * @return List of colors.
+     * @hide
+     */
+    public @NonNull List<Color> getMainColors() {
+        return Collections.unmodifiableList(mMainColors);
     }
 
     @Override
@@ -118,38 +293,91 @@
         }
 
         WallpaperColors other = (WallpaperColors) o;
-        return mColors.equals(other.mColors) && mSupportsDarkText == other.mSupportsDarkText;
+        return mMainColors.equals(other.mMainColors)
+                && mColorHints == other.mColorHints;
     }
 
     @Override
     public int hashCode() {
-        return 31 * mColors.hashCode() + (mSupportsDarkText ? 1 : 0);
+        return 31 * mMainColors.hashCode() + mColorHints;
     }
 
     /**
-     * Whether or not dark text is legible on top of this wallpaper.
+     * Combination of WallpaperColor hints.
      *
-     * @return true if dark text is supported
+     * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT
+     * @return True if dark text is supported.
      */
-    public boolean supportsDarkText() {
-        return mSupportsDarkText;
+    public int getColorHints() {
+        return mColorHints;
     }
 
-    private static boolean calculateDarkTextSupport(List<Pair<Color, Integer>> colors) {
-        if (colors == null) {
+    /**
+     * @param colorHints Combination of WallpaperColors hints.
+     * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT
+     * @hide
+     */
+    public void setColorHints(int colorHints) {
+        mColorHints = colorHints;
+    }
+
+    /**
+     * Checks if image is bright and clean enough to support light text.
+     *
+     * @param source What to read.
+     * @return Whether image supports dark text or not.
+     */
+    private static boolean calculateDarkTextSupport(Bitmap source) {
+        if (source == null) {
             return false;
         }
 
-        Pair<Color, Integer> mainColor = null;
+        int[] pixels = new int[source.getWidth() * source.getHeight()];
+        double totalLuminance = 0;
+        final int maxDarkPixels = (int) (pixels.length * MAX_DARK_AREA);
+        int darkPixels = 0;
+        source.getPixels(pixels, 0 /* offset */, source.getWidth(), 0 /* x */, 0 /* y */,
+                source.getWidth(), source.getHeight());
 
-        for (Pair<Color, Integer> color : colors) {
-            if (mainColor == null) {
-                mainColor = color;
-            } else if (color.second > mainColor.second) {
-                mainColor = color;
+        // This bitmap was already resized to fit the maximum allowed area.
+        // Let's just loop through the pixels, no sweat!
+        for (int i = 0; i < pixels.length; i++) {
+            final float luminance = Color.luminance(pixels[i]);
+            final int alpha = Color.alpha(pixels[i]);
+
+            // Make sure we don't have a dark pixel mass that will
+            // make text illegible.
+            if (luminance < DARK_PIXEL_LUMINANCE && alpha != 0) {
+                darkPixels++;
+                if (darkPixels > maxDarkPixels) {
+                    return false;
+                }
             }
+
+            totalLuminance += luminance;
         }
-        return mainColor != null &&
-                mainColor.first.luminance() > BRIGHT_LUMINANCE;
+        return totalLuminance / pixels.length > BRIGHT_IMAGE_MEAN_LUMINANCE;
+    }
+
+    private static Size calculateOptimalSize(int width, int height) {
+        // Calculate how big the bitmap needs to be.
+        // This avoids unnecessary processing and allocation inside Palette.
+        final int requestedArea = width * height;
+        double scale = 1;
+        if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) {
+            scale = Math.sqrt(MAX_WALLPAPER_EXTRACTION_AREA / (double) requestedArea);
+        }
+        int newWidth = (int) (width * scale);
+        int newHeight = (int) (height * scale);
+        // Dealing with edge cases of the drawable being too wide or too tall.
+        // Width or height would end up being 0, in this case we'll set it to 1.
+        if (newWidth == 0) {
+            newWidth = 1;
+        }
+        if (newHeight == 0) {
+            newHeight = 1;
+        }
+
+        return new Size(newWidth, newHeight);
     }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2f79538..5929aca 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -487,27 +487,27 @@
      */
     public abstract Context getApplicationContext();
 
-    /** Non-activity related accessibility ids are unique in the app */
-    private static int sLastAccessibilityId = View.NO_ID;
+    /** Non-activity related autofill ids are unique in the app */
+    private static int sLastAutofillId = View.NO_ID;
 
     /**
-     * Gets the next accessibility ID.
+     * Gets the next autofill ID.
      *
-     * <p>All IDs will be smaller or the same as {@link View#LAST_APP_ACCESSIBILITY_ID}. All IDs
+     * <p>All IDs will be smaller or the same as {@link View#LAST_APP_AUTOFILL_ID}. All IDs
      * returned will be unique.
      *
      * @return A ID that is unique in the process
      *
      * {@hide}
      */
-    public int getNextAccessibilityId() {
-        if (sLastAccessibilityId == View.LAST_APP_ACCESSIBILITY_ID - 1) {
-            sLastAccessibilityId = View.NO_ID;
+    public int getNextAutofillId() {
+        if (sLastAutofillId == View.LAST_APP_AUTOFILL_ID - 1) {
+            sLastAutofillId = View.NO_ID;
         }
 
-        sLastAccessibilityId++;
+        sLastAutofillId++;
 
-        return sLastAccessibilityId;
+        return sLastAutofillId;
     }
 
     /**
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 20fafb2..c719c64 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -961,8 +961,7 @@
     /**
      * @hide
      */
-    @Override
-    public int getNextAccessibilityId() {
-        return mBase.getNextAccessibilityId();
+    public int getNextAutofillId() {
+        return mBase.getNextAutofillId();
     }
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
index 15dbf26..fec7fd9 100644
--- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
@@ -96,6 +96,9 @@
         CaptureRequest.Builder singleTargetRequestBuilder = new CaptureRequest.Builder(
                 requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE);
 
+        // Carry over userTag, as native metadata doesn't have this field.
+        singleTargetRequestBuilder.setTag(request.getTag());
+
         // Overwrite the capture intent to make sure a good value is set.
         Iterator<Surface> iterator = outputSurfaces.iterator();
         Surface firstSurface = iterator.next();
@@ -118,6 +121,7 @@
             requestMetadata = new CameraMetadataNative(request.getNativeCopy());
             doubleTargetRequestBuilder = new CaptureRequest.Builder(
                     requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE);
+            doubleTargetRequestBuilder.setTag(request.getTag());
             doubleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
                     CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
             doubleTargetRequestBuilder.addTarget(firstSurface);
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 8678d95..b69a23a 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -696,6 +696,14 @@
     }
 
     /**
+     * Return whether there are any messages or callbacks currently scheduled on this handler.
+     * @hide
+     */
+    public final boolean hasMessagesOrCallbacks() {
+        return mQueue.hasMessages(this);
+    }
+
+    /**
      * Check if there are any pending posts of messages with code 'what' and
      * whose obj is 'object' in the message queue.
      */
@@ -728,6 +736,18 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    public final void dumpMine(Printer pw, String prefix) {
+        pw.println(prefix + this + " @ " + SystemClock.uptimeMillis());
+        if (mLooper == null) {
+            pw.println(prefix + "looper uninitialized");
+        } else {
+            mLooper.dump(pw, prefix + "  ", this);
+        }
+    }
+
     @Override
     public String toString() {
         return "Handler (" + getClass().getName() + ") {"
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 44dbcfb..04cceb8 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -310,7 +310,20 @@
      */
     public void dump(@NonNull Printer pw, @NonNull String prefix) {
         pw.println(prefix + toString());
-        mQueue.dump(pw, prefix + "  ");
+        mQueue.dump(pw, prefix + "  ", null);
+    }
+
+    /**
+     * Dumps the state of the looper for debugging purposes.
+     *
+     * @param pw A printer to receive the contents of the dump.
+     * @param prefix A prefix to prepend to each line which is printed.
+     * @param handler Only dump messages for this Handler.
+     * @hide
+     */
+    public void dump(@NonNull Printer pw, @NonNull String prefix, Handler handler) {
+        pw.println(prefix + toString());
+        mQueue.dump(pw, prefix + "  ", handler);
     }
 
     /** @hide */
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 2a8c52e..624e28a 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -620,6 +620,23 @@
         }
     }
 
+    boolean hasMessages(Handler h) {
+        if (h == null) {
+            return false;
+        }
+
+        synchronized (this) {
+            Message p = mMessages;
+            while (p != null) {
+                if (p.target == h) {
+                    return true;
+                }
+                p = p.next;
+            }
+            return false;
+        }
+    }
+
     void removeMessages(Handler h, int what, Object object) {
         if (h == null) {
             return;
@@ -759,12 +776,14 @@
         }
     }
 
-    void dump(Printer pw, String prefix) {
+    void dump(Printer pw, String prefix, Handler h) {
         synchronized (this) {
             long now = SystemClock.uptimeMillis();
             int n = 0;
             for (Message msg = mMessages; msg != null; msg = msg.next) {
-                pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+                if (h == null || h == msg.target) {
+                    pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+                }
                 n++;
             }
             pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 921d518..bdc27f2 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9130,7 +9130,10 @@
          * <pre>
          * max_cached_processes                 (int)
          * background_settle_time               (long)
-         * foreground_service_ui_min_time       (long)
+         * fgservice_min_shown_time             (long)
+         * fgservice_min_report_time            (long)
+         * fgservice_screen_on_before_time      (long)
+         * fgservice_screen_on_after_time       (long)
          * content_provider_retain_time         (long)
          * gc_timeout                           (long)
          * gc_min_interval                      (long)
@@ -9936,6 +9939,16 @@
         public static final String ENABLE_EPHEMERAL_FEATURE = "enable_ephemeral_feature";
 
         /**
+         * Toggle to enable/disable dexopt for instant applications. The default is for dexopt
+         * to be disabled.
+         * <p>
+         * Type: int (0 to disable, 1 to enable)
+         *
+         * @hide
+         */
+        public static final String INSTANT_APP_DEXOPT_ENABLED = "instant_app_dexopt_enabled";
+
+        /**
          * The min period for caching installed instant apps in milliseconds.
          * <p>
          * Type: long
@@ -10085,6 +10098,15 @@
             "backup_refactored_service_disabled";
 
         /**
+         * Flag to set the waiting time for euicc factory reset inside System > Settings
+         * Type: long
+         *
+         * @hide
+         */
+        public static final String EUICC_WIPING_TIMEOUT_MILLIS =
+                "euicc_wiping_timeout_millis";
+
+        /**
          * Settings to backup. This is here so that it's in the same place as the settings
          * keys and easy to update.
          *
@@ -10670,6 +10692,13 @@
          */
         public static final String ENABLE_CACHE_QUOTA_CALCULATION =
                 "enable_cache_quota_calculation";
+
+        /**
+         * Whether the Deletion Helper no threshold toggle is available.
+         * @hide
+         */
+        public static final String ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE =
+                "enable_deletion_helper_no_threshold_toggle";
     }
 
     /**
diff --git a/core/java/android/service/autofill/FillContext.java b/core/java/android/service/autofill/FillContext.java
index 6956c8a..f8a8751 100644
--- a/core/java/android/service/autofill/FillContext.java
+++ b/core/java/android/service/autofill/FillContext.java
@@ -106,15 +106,15 @@
     }
 
     /**
-     * Finds {@link ViewNode}s that have the requested ids.
+     * Finds {@link ViewNode ViewNodes} that have the requested ids.
      *
-     * @param ids The ids of the node to find
+     * @param ids The ids of the node to find.
      *
-     * @return The nodes indexed in the same way as the ids
+     * @return The nodes indexed in the same way as the ids.
      *
      * @hide
      */
-    @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId... ids) {
+    @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId[] ids) {
         final LinkedList<ViewNode> nodesToProcess = new LinkedList<>();
         final ViewNode[] foundNodes = new AssistStructure.ViewNode[ids.length];
 
@@ -178,6 +178,30 @@
         return foundNodes;
     }
 
+    /**
+     * Finds the {@link ViewNode} that has the requested {@code id}, if any.
+     *
+     * @hide
+     */
+    @Nullable public ViewNode findViewNodeByAutofillId(@NonNull AutofillId id) {
+        final LinkedList<ViewNode> nodesToProcess = new LinkedList<>();
+        final int numWindowNodes = mStructure.getWindowNodeCount();
+        for (int i = 0; i < numWindowNodes; i++) {
+            nodesToProcess.add(mStructure.getWindowNodeAt(i).getRootViewNode());
+        }
+        while (!nodesToProcess.isEmpty()) {
+            final ViewNode node = nodesToProcess.removeFirst();
+            if (id.equals(node.getAutofillId())) {
+                return node;
+            }
+            for (int i = 0; i < node.getChildCount(); i++) {
+                nodesToProcess.addLast(node.getChildAt(i));
+            }
+        }
+
+        return null;
+    }
+
     public static final Parcelable.Creator<FillContext> CREATOR =
             new Parcelable.Creator<FillContext>() {
         @Override
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index fa3f55b..6ea7d5e 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -273,13 +273,24 @@
          *
          * <p>See {@link SaveInfo} for more info.
          *
-         * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty.
+         * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty, or if
+         * it contains any {@code null} entry.
          */
         public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) {
-            Preconditions.checkArgument(requiredIds != null && requiredIds.length > 0,
-                    "must have at least one required id: " + Arrays.toString(requiredIds));
+            // TODO: add CTS unit tests (not integration) to assert the null cases
             mType = type;
-            mRequiredIds = requiredIds;
+            mRequiredIds = assertValid(requiredIds);
+        }
+
+        private AutofillId[] assertValid(AutofillId[] ids) {
+            Preconditions.checkArgument(ids != null && ids.length > 0,
+                    "must have at least one id: " + Arrays.toString(ids));
+            for (int i = 0; i < ids.length; i++) {
+                final AutofillId id = ids[i];
+                Preconditions.checkArgument(id != null,
+                        "cannot have null id: " + Arrays.toString(ids));
+            }
+            return ids;
         }
 
         /**
@@ -302,12 +313,14 @@
          *
          * @param ids The ids of the optional views.
          * @return This builder.
+         *
+         * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if
+         * it contains any {@code null} entry.
          */
-        public @NonNull Builder setOptionalIds(@Nullable AutofillId[] ids) {
+        public @NonNull Builder setOptionalIds(@NonNull AutofillId[] ids) {
+            // TODO: add CTS unit tests (not integration) to assert the null cases
             throwIfDestroyed();
-            if (ids != null && ids.length != 0) {
-                mOptionalIds = ids;
-            }
+            mOptionalIds = assertValid(ids);
             return this;
         }
 
@@ -421,7 +434,10 @@
             final Builder builder = new Builder(parcel.readInt(),
                     parcel.readParcelableArray(null, AutofillId.class));
             builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null));
-            builder.setOptionalIds(parcel.readParcelableArray(null, AutofillId.class));
+            final AutofillId[] optionalIds = parcel.readParcelableArray(null, AutofillId.class);
+            if (optionalIds != null) {
+                builder.setOptionalIds(optionalIds);
+            }
             builder.setDescription(parcel.readCharSequence());
             builder.setFlags(parcel.readInt());
             return builder.build();
diff --git a/core/java/android/service/euicc/DeleteResult.aidl b/core/java/android/service/euicc/DeleteResult.aidl
deleted file mode 100644
index 3da8b49..0000000
--- a/core/java/android/service/euicc/DeleteResult.aidl
+++ /dev/null
@@ -1,19 +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.service.euicc;
-
-parcelable DeleteResult;
diff --git a/core/java/android/service/euicc/DeleteResult.java b/core/java/android/service/euicc/DeleteResult.java
deleted file mode 100644
index 8be9ac9..0000000
--- a/core/java/android/service/euicc/DeleteResult.java
+++ /dev/null
@@ -1,97 +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.service.euicc;
-
-import android.annotation.IntDef;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Result of a {@link EuiccService#onDeleteSubscription} operation.
- * @hide
- *
- * TODO(b/35851809): Make this a SystemApi.
- */
-public final class DeleteResult implements Parcelable {
-
-    public static final Creator<DeleteResult> CREATOR = new Creator<DeleteResult>() {
-        @Override
-        public DeleteResult createFromParcel(Parcel in) {
-            return new DeleteResult(in);
-        }
-
-        @Override
-        public DeleteResult[] newArray(int size) {
-            return new DeleteResult[size];
-        }
-    };
-
-    /** @hide */
-    @IntDef({
-            RESULT_OK,
-            RESULT_GENERIC_ERROR,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ResultCode {}
-
-    public static final int RESULT_OK = 0;
-    public static final int RESULT_GENERIC_ERROR = 1;
-
-    /** Result of the operation - one of the RESULT_* constants. */
-    public final @ResultCode int result;
-
-    /** Implementation-defined detailed error code in case of a failure not covered here. */
-    public final int detailedCode;
-
-    private DeleteResult(int result, int detailedCode) {
-        this.result = result;
-        this.detailedCode = detailedCode;
-    }
-
-    private DeleteResult(Parcel in) {
-        this.result = in.readInt();
-        this.detailedCode = in.readInt();
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(result);
-        dest.writeInt(detailedCode);
-    }
-
-    /** Return a result indicating that the delete was successful. */
-    public static DeleteResult success() {
-        return new DeleteResult(RESULT_OK, 0);
-    }
-
-    /**
-     * Return a result indicating that an error occurred for which no other more specific error
-     * code has been defined.
-     *
-     * @param detailedCode an implemenation-defined detailed error code for debugging purposes.
-     */
-    public static DeleteResult genericError(int detailedCode) {
-        return new DeleteResult(RESULT_GENERIC_ERROR, detailedCode);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/core/java/android/service/euicc/DownloadResult.aidl b/core/java/android/service/euicc/DownloadResult.aidl
deleted file mode 100644
index 66ec999..0000000
--- a/core/java/android/service/euicc/DownloadResult.aidl
+++ /dev/null
@@ -1,19 +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.service.euicc;
-
-parcelable DownloadResult;
diff --git a/core/java/android/service/euicc/DownloadResult.java b/core/java/android/service/euicc/DownloadResult.java
deleted file mode 100644
index ad75bff9..0000000
--- a/core/java/android/service/euicc/DownloadResult.java
+++ /dev/null
@@ -1,106 +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.service.euicc;
-
-import android.annotation.IntDef;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Result of a {@link EuiccService#onDownloadSubscription} operation.
- * @hide
- *
- * TODO(b/35851809): Make this a SystemApi.
- */
-public final class DownloadResult implements Parcelable {
-
-    public static final Creator<DownloadResult> CREATOR = new Creator<DownloadResult>() {
-        @Override
-        public DownloadResult createFromParcel(Parcel in) {
-            return new DownloadResult(in);
-        }
-
-        @Override
-        public DownloadResult[] newArray(int size) {
-            return new DownloadResult[size];
-        }
-    };
-
-    /** @hide */
-    @IntDef({
-            RESULT_OK,
-            RESULT_GENERIC_ERROR,
-            RESULT_MUST_DEACTIVATE_SIM,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ResultCode {}
-
-    public static final int RESULT_OK = 0;
-    public static final int RESULT_GENERIC_ERROR = 1;
-    public static final int RESULT_MUST_DEACTIVATE_SIM = 2;
-
-    /** Result of the operation - one of the RESULT_* constants. */
-    public final @ResultCode int result;
-
-    /** Implementation-defined detailed error code in case of a failure not covered here. */
-    public final int detailedCode;
-
-    private DownloadResult(int result, int detailedCode) {
-        this.result = result;
-        this.detailedCode = detailedCode;
-    }
-
-    private DownloadResult(Parcel in) {
-        this.result = in.readInt();
-        this.detailedCode = in.readInt();
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(result);
-        dest.writeInt(detailedCode);
-    }
-
-    /** Return a result indicating that the download was successful. */
-    public static DownloadResult success() {
-        return new DownloadResult(RESULT_OK, 0);
-    }
-
-    /**
-     * Return a result indicating that an active SIM must be deactivated to perform the operation.
-     */
-    public static DownloadResult mustDeactivateSim() {
-        return new DownloadResult(RESULT_MUST_DEACTIVATE_SIM, 0);
-    }
-
-    /**
-     * Return a result indicating that an error occurred for which no other more specific error
-     * code has been defined.
-     *
-     * @param detailedCode an implemenation-defined detailed error code for debugging purposes.
-     */
-    public static DownloadResult genericError(int detailedCode) {
-        return new DownloadResult(RESULT_GENERIC_ERROR, detailedCode);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/core/java/android/service/euicc/EraseResult.java b/core/java/android/service/euicc/EraseResult.java
deleted file mode 100644
index 1cce5a9..0000000
--- a/core/java/android/service/euicc/EraseResult.java
+++ /dev/null
@@ -1,97 +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.service.euicc;
-
-import android.annotation.IntDef;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Result of a {@link EuiccService#onEraseSubscriptions} operation.
- * @hide
- *
- * TODO(b/35851809): Make this a SystemApi.
- */
-public final class EraseResult implements Parcelable {
-
-    public static final Creator<EraseResult> CREATOR = new Creator<EraseResult>() {
-        @Override
-        public EraseResult createFromParcel(Parcel in) {
-            return new EraseResult(in);
-        }
-
-        @Override
-        public EraseResult[] newArray(int size) {
-            return new EraseResult[size];
-        }
-    };
-
-    /** @hide */
-    @IntDef({
-            RESULT_OK,
-            RESULT_GENERIC_ERROR,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ResultCode {}
-
-    public static final int RESULT_OK = 0;
-    public static final int RESULT_GENERIC_ERROR = 1;
-
-    /** Result of the operation - one of the RESULT_* constants. */
-    public final @ResultCode int result;
-
-    /** Implementation-defined detailed error code in case of a failure not covered here. */
-    public final int detailedCode;
-
-    private EraseResult(int result, int detailedCode) {
-        this.result = result;
-        this.detailedCode = detailedCode;
-    }
-
-    private EraseResult(Parcel in) {
-        this.result = in.readInt();
-        this.detailedCode = in.readInt();
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(result);
-        dest.writeInt(detailedCode);
-    }
-
-    /** Return a result indicating that the erase was successful. */
-    public static EraseResult success() {
-        return new EraseResult(RESULT_OK, 0);
-    }
-
-    /**
-     * Return a result indicating that an error occurred for which no other more specific error
-     * code has been defined.
-     *
-     * @param detailedCode an implemenation-defined detailed error code for debugging purposes.
-     */
-    public static EraseResult genericError(int detailedCode) {
-        return new EraseResult(RESULT_GENERIC_ERROR, detailedCode);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index 3734904..875f286 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -25,6 +25,12 @@
 import android.telephony.euicc.EuiccInfo;
 import android.util.ArraySet;
 
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
 /**
  * Service interface linking the system with an eUICC local profile assistant (LPA) application.
  *
@@ -90,6 +96,16 @@
      */
     public static final String ACTION_RESOLVE_NO_PRIVILEGES =
             "android.service.euicc.action.RESOLVE_NO_PRIVILEGES";
+
+    /** Result code for a successful operation. */
+    public static final int RESULT_OK = 0;
+    /** Result code indicating that an active SIM must be deactivated to perform the operation. */
+    public static final int RESULT_MUST_DEACTIVATE_SIM = -1;
+    // New predefined codes should have negative values.
+
+    /** Start of implementation-specific error results. */
+    public static final int RESULT_FIRST_USER = 1;
+
     /**
      * List of all valid resolution actions for validation purposes.
      * @hide
@@ -106,10 +122,45 @@
 
     private final IEuiccService.Stub mStubWrapper;
 
+    private ThreadPoolExecutor mExecutor;
+
     public EuiccService() {
         mStubWrapper = new IEuiccServiceWrapper();
     }
 
+    @Override
+    @CallSuper
+    public void onCreate() {
+        super.onCreate();
+        // We use a oneway AIDL interface to avoid blocking phone process binder threads on IPCs to
+        // an external process, but doing so means the requests are serialized by binder, which is
+        // not desired. Spin up a background thread pool to allow requests to be parallelized.
+        // TODO(b/38206971): Consider removing this if basic card-level functions like listing
+        // profiles are moved to the platform.
+        mExecutor = new ThreadPoolExecutor(
+                4 /* corePoolSize */,
+                4 /* maxPoolSize */,
+                30, TimeUnit.SECONDS, /* keepAliveTime */
+                new LinkedBlockingQueue<>(), /* workQueue */
+                new ThreadFactory() {
+                    private final AtomicInteger mCount = new AtomicInteger(1);
+
+                    @Override
+                    public Thread newThread(Runnable r) {
+                        return new Thread(r, "EuiccService #" + mCount.getAndIncrement());
+                    }
+                }
+        );
+        mExecutor.allowCoreThreadTimeOut(true);
+    }
+
+    @Override
+    @CallSuper
+    public void onDestroy() {
+        mExecutor.shutdownNow();
+        super.onDestroy();
+    }
+
     /**
      * If overriding this method, call through to the super method for any unknown actions.
      * {@inheritDoc}
@@ -138,9 +189,8 @@
      *     but is here to future-proof the APIs.
      * @param subscription A subscription whose metadata needs to be populated.
      * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
-     *     eUICC, perform this action automatically. Otherwise,
-     *     {@link GetDownloadableSubscriptionMetadataResult#mustDeactivateSim()} should be returned
-     *     to allow the user to consent to this operation first.
+     *     eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM)}
+     *     should be returned to allow the user to consent to this operation first.
      * @return The result of the operation.
      * @see android.telephony.euicc.EuiccManager#getDownloadableSubscriptionMetadata
      */
@@ -153,9 +203,8 @@
      * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
      *     but is here to future-proof the APIs.
      * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
-     *     eUICC, perform this action automatically. Otherwise,
-     *     {@link GetDefaultDownloadableSubscriptionListResult#mustDeactivateSim()} should be
-     *     returned to allow the user to consent to this operation first.
+     *     eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM)}
+     *     should be returned to allow the user to consent to this operation first.
      * @return The result of the list operation.
      * @see android.telephony.euicc.EuiccManager#getDefaultDownloadableSubscriptionList
      */
@@ -171,13 +220,13 @@
      * @param switchAfterDownload If true, the subscription should be enabled upon successful
      *     download.
      * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
-     *     eUICC, perform this action automatically. Otherwise,
-     *     {@link DownloadResult#mustDeactivateSim()} should be returned to allow the user to
-     *     consent to this operation first.
-     * @return the result of the download operation.
+     *     eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM}
+     *     should be returned to allow the user to consent to this operation first.
+     * @return the result of the download operation. May be one of the predefined {@code RESULT_}
+     *     constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
      * @see android.telephony.euicc.EuiccManager#downloadSubscription
      */
-    public abstract DownloadResult onDownloadSubscription(int slotId,
+    public abstract int onDownloadSubscription(int slotId,
             DownloadableSubscription subscription, boolean switchAfterDownload,
             boolean forceDeactivateSim);
 
@@ -211,10 +260,11 @@
      * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
      *     but is here to future-proof the APIs.
      * @param iccid the ICCID of the subscription to delete.
-     * @return the result of the delete operation.
+     * @return the result of the delete operation. May be one of the predefined {@code RESULT_}
+     *     constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
      * @see android.telephony.euicc.EuiccManager#deleteSubscription
      */
-    public abstract DeleteResult onDeleteSubscription(int slotId, String iccid);
+    public abstract int onDeleteSubscription(int slotId, String iccid);
 
     /**
      * Switch to the given subscription.
@@ -225,13 +275,13 @@
      *     profile should be deactivated and no profile should be activated to replace it - this is
      *     equivalent to a physical SIM being ejected.
      * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
-     *     eUICC, perform this action automatically. Otherwise,
-     *     {@link SwitchResult#mustDeactivateSim()} should be returned to allow the user to consent
-     *     to this operation first.
-     * @return the result of the switch operation.
+     *     eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM}
+     *     should be returned to allow the user to consent to this operation first.
+     * @return the result of the switch operation. May be one of the predefined {@code RESULT_}
+     *     constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
      * @see android.telephony.euicc.EuiccManager#switchToSubscription
      */
-    public abstract SwitchResult onSwitchToSubscription(int slotId, @Nullable String iccid,
+    public abstract int onSwitchToSubscription(int slotId, @Nullable String iccid,
             boolean forceDeactivateSim);
 
     /**
@@ -241,10 +291,11 @@
      *     but is here to future-proof the APIs.
      * @param iccid the ICCID of the subscription to update.
      * @param nickname the new nickname to apply.
-     * @return the result of the update operation.
+     * @return the result of the update operation. May be one of the predefined {@code RESULT_}
+     *     constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
      * @see android.telephony.euicc.EuiccManager#updateSubscriptionNickname
      */
-    public abstract UpdateNicknameResult onUpdateSubscriptionNickname(int slotId, String iccid,
+    public abstract int onUpdateSubscriptionNickname(int slotId, String iccid,
             String nickname);
 
     /**
@@ -255,10 +306,11 @@
      *
      * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
      *     but is here to future-proof the APIs.
-     * @return the result of the erase operation.
+     * @return the result of the erase operation. May be one of the predefined {@code RESULT_}
+     *     constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
      * @see android.telephony.euicc.EuiccManager#eraseSubscriptions
      */
-    public abstract EraseResult onEraseSubscriptions(int slotId);
+    public abstract int onEraseSubscriptions(int slotId);
 
     /**
      * Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}.
@@ -268,23 +320,33 @@
         public void downloadSubscription(int slotId, DownloadableSubscription subscription,
                 boolean switchAfterDownload, boolean forceDeactivateSim,
                 IDownloadSubscriptionCallback callback) {
-            DownloadResult result = EuiccService.this.onDownloadSubscription(
-                    slotId, subscription, switchAfterDownload, forceDeactivateSim);
-            try {
-                callback.onComplete(result);
-            } catch (RemoteException e) {
-                // Can't communicate with the phone process; ignore.
-            }
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    int result = EuiccService.this.onDownloadSubscription(
+                            slotId, subscription, switchAfterDownload, forceDeactivateSim);
+                    try {
+                        callback.onComplete(result);
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
         }
 
         @Override
         public void getEid(int slotId, IGetEidCallback callback) {
-            String eid = EuiccService.this.onGetEid(slotId);
-            try {
-                callback.onSuccess(eid);
-            } catch (RemoteException e) {
-                // Can't communicate with the phone process; ignore.
-            }
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    String eid = EuiccService.this.onGetEid(slotId);
+                    try {
+                        callback.onSuccess(eid);
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
         }
 
         @Override
@@ -292,93 +354,135 @@
                 DownloadableSubscription subscription,
                 boolean forceDeactivateSim,
                 IGetDownloadableSubscriptionMetadataCallback callback) {
-            GetDownloadableSubscriptionMetadataResult result =
-                    EuiccService.this.onGetDownloadableSubscriptionMetadata(
-                            slotId, subscription, forceDeactivateSim);
-            try {
-                callback.onComplete(result);
-            } catch (RemoteException e) {
-                // Can't communicate with the phone process; ignore.
-            }
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    GetDownloadableSubscriptionMetadataResult result =
+                            EuiccService.this.onGetDownloadableSubscriptionMetadata(
+                                    slotId, subscription, forceDeactivateSim);
+                    try {
+                        callback.onComplete(result);
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
         }
 
         @Override
         public void getDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim,
                 IGetDefaultDownloadableSubscriptionListCallback callback) {
-            GetDefaultDownloadableSubscriptionListResult result =
-                    EuiccService.this.onGetDefaultDownloadableSubscriptionList(
-                            slotId, forceDeactivateSim);
-            try {
-                callback.onComplete(result);
-            } catch (RemoteException e) {
-                // Can't communicate with the phone process; ignore.
-            }
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    GetDefaultDownloadableSubscriptionListResult result =
+                            EuiccService.this.onGetDefaultDownloadableSubscriptionList(
+                                    slotId, forceDeactivateSim);
+                    try {
+                        callback.onComplete(result);
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
         }
 
         @Override
         public void getEuiccProfileInfoList(int slotId, IGetEuiccProfileInfoListCallback callback) {
-            GetEuiccProfileInfoListResult result =
-                    EuiccService.this.onGetEuiccProfileInfoList(slotId);
-            try {
-                callback.onComplete(result);
-            } catch (RemoteException e) {
-                // Can't communicate with the phone process; ignore.
-            }
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    GetEuiccProfileInfoListResult result =
+                            EuiccService.this.onGetEuiccProfileInfoList(slotId);
+                    try {
+                        callback.onComplete(result);
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
         }
 
         @Override
         public void getEuiccInfo(int slotId, IGetEuiccInfoCallback callback) {
-            EuiccInfo euiccInfo = EuiccService.this.onGetEuiccInfo(slotId);
-            try {
-                callback.onSuccess(euiccInfo);
-            } catch (RemoteException e) {
-                // Can't communicate with the phone process; ignore.
-            }
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    EuiccInfo euiccInfo = EuiccService.this.onGetEuiccInfo(slotId);
+                    try {
+                        callback.onSuccess(euiccInfo);
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
+
         }
 
         @Override
         public void deleteSubscription(int slotId, String iccid,
                 IDeleteSubscriptionCallback callback) {
-            DeleteResult result = EuiccService.this.onDeleteSubscription(slotId, iccid);
-            try {
-                callback.onComplete(result);
-            } catch (RemoteException e) {
-                // Can't communicate with the phone process; ignore.
-            }
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    int result = EuiccService.this.onDeleteSubscription(slotId, iccid);
+                    try {
+                        callback.onComplete(result);
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
         }
 
         @Override
         public void switchToSubscription(int slotId, String iccid, boolean forceDeactivateSim,
                 ISwitchToSubscriptionCallback callback) {
-            SwitchResult result =
-                    EuiccService.this.onSwitchToSubscription(slotId, iccid, forceDeactivateSim);
-            try {
-                callback.onComplete(result);
-            } catch (RemoteException e) {
-                // Can't communicate with the phone process; ignore.
-            }
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    int result =
+                            EuiccService.this.onSwitchToSubscription(
+                                    slotId, iccid, forceDeactivateSim);
+                    try {
+                        callback.onComplete(result);
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
         }
 
         @Override
         public void updateSubscriptionNickname(int slotId, String iccid, String nickname,
                 IUpdateSubscriptionNicknameCallback callback) {
-            UpdateNicknameResult result =
-                    EuiccService.this.onUpdateSubscriptionNickname(slotId, iccid, nickname);
-            try {
-                callback.onComplete(result);
-            } catch (RemoteException e) {
-                // Can't communicate with the phone process; ignore.
-            }
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    int result =
+                            EuiccService.this.onUpdateSubscriptionNickname(slotId, iccid, nickname);
+                    try {
+                        callback.onComplete(result);
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
         }
 
         @Override
         public void eraseSubscriptions(int slotId, IEraseSubscriptionsCallback callback) {
-            EraseResult result = EuiccService.this.onEraseSubscriptions(slotId);
-            try {
-                callback.onComplete(result);
-            } catch (RemoteException e) {
-                // Can't communicate with the phone process; ignore.
-            }
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    int result = EuiccService.this.onEraseSubscriptions(slotId);
+                    try {
+                        callback.onComplete(result);
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
         }
     }
 }
diff --git a/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java b/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
index 95569b2..5a24492 100644
--- a/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
+++ b/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
@@ -15,15 +15,11 @@
  */
 package android.service.euicc;
 
-import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.euicc.DownloadableSubscription;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 /**
  * Result of a {@link EuiccService#onGetDefaultDownloadableSubscriptionList} operation.
  * @hide
@@ -45,77 +41,54 @@
         }
     };
 
-    /** @hide */
-    @IntDef({
-            RESULT_OK,
-            RESULT_GENERIC_ERROR,
-            RESULT_MUST_DEACTIVATE_SIM,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ResultCode {}
-
-    public static final int RESULT_OK = 0;
-    public static final int RESULT_MUST_DEACTIVATE_SIM = 1;
-    public static final int RESULT_GENERIC_ERROR = 2;
-
-    /** Result of the operation - one of the RESULT_* constants. */
-    public final @ResultCode int result;
+    /**
+     * Result of the operation.
+     *
+     * <p>May be one of the predefined {@code RESULT_} constants in EuiccService or any
+     * implementation-specific code starting with {@link EuiccService#RESULT_FIRST_USER}.
+     */
+    public final int result;
 
     /**
      * The available {@link DownloadableSubscription}s (with filled-in metadata).
      *
-     * <p>Only non-null if {@link #result} is {@link #RESULT_OK}.
+     * <p>Only non-null if {@link #result} is {@link EuiccService#RESULT_OK}.
      */
     @Nullable
     public final DownloadableSubscription[] subscriptions;
 
-    /** Implementation-defined detailed error code in case of a failure not covered here. */
-    public final int detailedCode;
-
-    private GetDefaultDownloadableSubscriptionListResult(int result,
-            @Nullable DownloadableSubscription[] subscriptions, int detailedCode) {
+    /**
+     * Construct a new {@link GetDefaultDownloadableSubscriptionListResult}.
+     *
+     * @param result Result of the operation. May be one of the predefined {@code RESULT_} constants
+     *     in EuiccService or any implementation-specific code starting with
+     *     {@link EuiccService#RESULT_FIRST_USER}.
+     * @param subscriptions The available subscriptions. Should only be provided if the result is
+     *     {@link EuiccService#RESULT_OK}.
+     */
+    public GetDefaultDownloadableSubscriptionListResult(int result,
+            @Nullable DownloadableSubscription[] subscriptions) {
         this.result = result;
-        this.subscriptions = subscriptions;
-        this.detailedCode = detailedCode;
+        if (this.result == EuiccService.RESULT_OK) {
+            this.subscriptions = subscriptions;
+        } else {
+            if (subscriptions != null) {
+                throw new IllegalArgumentException(
+                        "Error result with non-null subscriptions: " + result);
+            }
+            this.subscriptions = null;
+        }
     }
 
     private GetDefaultDownloadableSubscriptionListResult(Parcel in) {
         this.result = in.readInt();
         this.subscriptions = in.createTypedArray(DownloadableSubscription.CREATOR);
-        this.detailedCode = in.readInt();
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(result);
         dest.writeTypedArray(subscriptions, flags);
-        dest.writeInt(detailedCode);
-    }
-
-    /** Return a result indicating that the list operation was successful. */
-    public static GetDefaultDownloadableSubscriptionListResult success(
-            DownloadableSubscription[] subscriptions) {
-        return new GetDefaultDownloadableSubscriptionListResult(RESULT_OK, subscriptions,
-                0 /* detailedCode */);
-    }
-
-    /**
-     * Return a result indicating that an active SIM must be deactivated to perform the operation.
-     */
-    public static GetDefaultDownloadableSubscriptionListResult mustDeactivateSim() {
-        return new GetDefaultDownloadableSubscriptionListResult(RESULT_MUST_DEACTIVATE_SIM,
-                null /* subscription */, 0 /* detailedCode */);
-    }
-
-    /**
-     * Return a result indicating that an error occurred for which no other more specific error
-     * code has been defined.
-     *
-     * @param detailedCode an implementation-defined detailed error code for debugging purposes.
-     */
-    public static GetDefaultDownloadableSubscriptionListResult genericError(int detailedCode) {
-        return new GetDefaultDownloadableSubscriptionListResult(RESULT_GENERIC_ERROR,
-                null /* subscription */, detailedCode);
     }
 
     @Override
diff --git a/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java b/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
index 99e7614..de8a307 100644
--- a/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
+++ b/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
@@ -15,15 +15,11 @@
  */
 package android.service.euicc;
 
-import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.euicc.DownloadableSubscription;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 /**
  * Result of a {@link EuiccService#onGetDownloadableSubscriptionMetadata} operation.
  * @hide
@@ -45,77 +41,54 @@
         }
     };
 
-    /** @hide */
-    @IntDef({
-            RESULT_OK,
-            RESULT_GENERIC_ERROR,
-            RESULT_MUST_DEACTIVATE_SIM,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ResultCode {}
-
-    public static final int RESULT_OK = 0;
-    public static final int RESULT_MUST_DEACTIVATE_SIM = 1;
-    public static final int RESULT_GENERIC_ERROR = 2;
-
-    /** Result of the operation - one of the RESULT_* constants. */
-    public final @ResultCode int result;
+    /**
+     * Result of the operation.
+     *
+     * <p>May be one of the predefined {@code RESULT_} constants in EuiccService or any
+     * implementation-specific code starting with {@link EuiccService#RESULT_FIRST_USER}.
+     */
+    public final int result;
 
     /**
      * The {@link DownloadableSubscription} with filled-in metadata.
      *
-     * <p>Only non-null if {@link #result} is {@link #RESULT_OK}.
+     * <p>Only non-null if {@link #result} is {@link EuiccService#RESULT_OK}.
      */
     @Nullable
     public final DownloadableSubscription subscription;
 
-    /** Implementation-defined detailed error code in case of a failure not covered here. */
-    public final int detailedCode;
-
-    private GetDownloadableSubscriptionMetadataResult(int result,
-            @Nullable DownloadableSubscription subscription, int detailedCode) {
+    /**
+     * Construct a new {@link GetDownloadableSubscriptionMetadataResult}.
+     *
+     * @param result Result of the operation. May be one of the predefined {@code RESULT_} constants
+     *     in EuiccService or any implementation-specific code starting with
+     *     {@link EuiccService#RESULT_FIRST_USER}.
+     * @param subscription The subscription with filled-in metadata. Should only be provided if the
+     *     result is {@link EuiccService#RESULT_OK}.
+     */
+    public GetDownloadableSubscriptionMetadataResult(int result,
+            @Nullable DownloadableSubscription subscription) {
         this.result = result;
-        this.subscription = subscription;
-        this.detailedCode = detailedCode;
+        if (this.result == EuiccService.RESULT_OK) {
+            this.subscription = subscription;
+        } else {
+            if (subscription != null) {
+                throw new IllegalArgumentException(
+                        "Error result with non-null subscription: " + result);
+            }
+            this.subscription = null;
+        }
     }
 
     private GetDownloadableSubscriptionMetadataResult(Parcel in) {
         this.result = in.readInt();
         this.subscription = in.readTypedObject(DownloadableSubscription.CREATOR);
-        this.detailedCode = in.readInt();
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(result);
         dest.writeTypedObject(this.subscription, flags);
-        dest.writeInt(detailedCode);
-    }
-
-    /** Return a result indicating that the lookup was successful. */
-    public static GetDownloadableSubscriptionMetadataResult success(
-            DownloadableSubscription subscription) {
-        return new GetDownloadableSubscriptionMetadataResult(RESULT_OK, subscription,
-                0 /* detailedCode */);
-    }
-
-    /**
-     * Return a result indicating that an active SIM must be deactivated to perform the operation.
-     */
-    public static GetDownloadableSubscriptionMetadataResult mustDeactivateSim() {
-        return new GetDownloadableSubscriptionMetadataResult(RESULT_MUST_DEACTIVATE_SIM,
-                null /* subscription */, 0 /* detailedCode */);
-    }
-
-    /**
-     * Return a result indicating that an error occurred for which no other more specific error
-     * code has been defined.
-     *
-     * @param detailedCode an implementation-defined detailed error code for debugging purposes.
-     */
-    public static GetDownloadableSubscriptionMetadataResult genericError(int detailedCode) {
-        return new GetDownloadableSubscriptionMetadataResult(RESULT_GENERIC_ERROR,
-                null /* subscription */, detailedCode);
     }
 
     @Override
diff --git a/core/java/android/service/euicc/GetEuiccProfileInfoListResult.java b/core/java/android/service/euicc/GetEuiccProfileInfoListResult.java
index 5ac10fe..7ad8488 100644
--- a/core/java/android/service/euicc/GetEuiccProfileInfoListResult.java
+++ b/core/java/android/service/euicc/GetEuiccProfileInfoListResult.java
@@ -15,14 +15,10 @@
  */
 package android.service.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;
-
 /**
  * Result of a {@link EuiccService#onGetEuiccProfileInfoList} operation.
  * @hide
@@ -33,33 +29,24 @@
 
     public static final Creator<GetEuiccProfileInfoListResult> CREATOR =
             new Creator<GetEuiccProfileInfoListResult>() {
-        @Override
-        public GetEuiccProfileInfoListResult createFromParcel(Parcel in) {
-            return new GetEuiccProfileInfoListResult(in);
-        }
+                @Override
+                public GetEuiccProfileInfoListResult createFromParcel(Parcel in) {
+                    return new GetEuiccProfileInfoListResult(in);
+                }
 
-        @Override
-        public GetEuiccProfileInfoListResult[] newArray(int size) {
-            return new GetEuiccProfileInfoListResult[size];
-        }
-    };
+                @Override
+                public GetEuiccProfileInfoListResult[] newArray(int size) {
+                    return new GetEuiccProfileInfoListResult[size];
+                }
+            };
 
-    /** @hide */
-    @IntDef({
-            RESULT_OK,
-            RESULT_GENERIC_ERROR,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ResultCode {}
-
-    public static final int RESULT_OK = 0;
-    public static final int RESULT_GENERIC_ERROR = 1;
-
-    /** Result of the operation - one of the RESULT_* constants. */
-    public final @ResultCode int result;
-
-    /** Implementation-defined detailed error code in case of a failure not covered here. */
-    public final int detailedCode;
+    /**
+     * Result of the operation.
+     *
+     * <p>May be one of the predefined {@code RESULT_} constants in EuiccService or any
+     * implementation-specific code starting with {@link EuiccService#RESULT_FIRST_USER}.
+     */
+    public final int result;
 
     /** The profile list (only upon success). */
     @Nullable
@@ -68,17 +55,37 @@
     /** Whether the eUICC is removable. */
     public final boolean isRemovable;
 
-    private GetEuiccProfileInfoListResult(int result, int detailedCode, EuiccProfileInfo[] profiles,
-            boolean isRemovable) {
+    /**
+     * Construct a new {@link GetEuiccProfileInfoListResult}.
+     *
+     * @param result Result of the operation. May be one of the predefined {@code RESULT_} constants
+     *     in EuiccService or any implementation-specific code starting with
+     *     {@link EuiccService#RESULT_FIRST_USER}.
+     * @param profiles the list of profiles. Should only be provided if the result is
+     *     {@link EuiccService#RESULT_OK}.
+     * @param isRemovable whether the eUICC in this slot is removable. If true, the profiles
+     *     returned here will only be considered accessible as long as this eUICC is present.
+     *     Otherwise, they will remain accessible until the next time a response with isRemovable
+     *     set to false is returned.
+     */
+    public GetEuiccProfileInfoListResult(
+            int result, @Nullable EuiccProfileInfo[] profiles, boolean isRemovable) {
         this.result = result;
-        this.detailedCode = detailedCode;
-        this.profiles = profiles;
         this.isRemovable = isRemovable;
+        if (this.result == EuiccService.RESULT_OK) {
+            this.profiles = profiles;
+        } else {
+            if (profiles != null) {
+                throw new IllegalArgumentException(
+                        "Error result with non-null profiles: " + result);
+            }
+            this.profiles = null;
+        }
+
     }
 
     private GetEuiccProfileInfoListResult(Parcel in) {
         this.result = in.readInt();
-        this.detailedCode = in.readInt();
         this.profiles = in.createTypedArray(EuiccProfileInfo.CREATOR);
         this.isRemovable = in.readBoolean();
     }
@@ -86,41 +93,10 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(result);
-        dest.writeInt(detailedCode);
         dest.writeTypedArray(profiles, flags);
         dest.writeBoolean(isRemovable);
     }
 
-    /**
-     * Return a result indicating that the listing was successful.
-     *
-     * @param profiles the list of profiles
-     * @param isRemovable whether the eUICC in this slot is removable. If true, the profiles
-     *     returned here will only be considered accessible as long as this eUICC is present.
-     *     Otherwise, they will remain accessible until the next time a response with isRemovable
-     *     set to false is returned.
-     */
-    public static GetEuiccProfileInfoListResult success(
-            EuiccProfileInfo[] profiles, boolean isRemovable) {
-        return new GetEuiccProfileInfoListResult(
-                RESULT_OK, 0 /* detailedCode */, profiles, isRemovable);
-    }
-
-    /**
-     * Return a result indicating that an error occurred for which no other more specific error
-     * code has been defined.
-     *
-     * @param detailedCode an implementation-defined detailed error code for debugging purposes.
-     * @param isRemovable whether the eUICC in this slot is removable. If true, only removable
-     *     profiles will be made inaccessible. Otherwise, all embedded profiles will be
-     *     considered inaccessible.
-     */
-    public static GetEuiccProfileInfoListResult genericError(
-            int detailedCode, boolean isRemovable) {
-        return new GetEuiccProfileInfoListResult(
-                RESULT_GENERIC_ERROR, detailedCode, null /* profiles */, isRemovable);
-    }
-
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl b/core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl
index 224cbd3..4667066 100644
--- a/core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl
+++ b/core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl
@@ -16,9 +16,7 @@
 
 package android.service.euicc;
 
-import android.service.euicc.DeleteResult;
-
 /** @hide */
 oneway interface IDeleteSubscriptionCallback {
-    void onComplete(in DeleteResult result);
+    void onComplete(int result);
 }
\ No newline at end of file
diff --git a/core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl b/core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl
index 0677cbe..6893c85 100644
--- a/core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl
+++ b/core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl
@@ -16,9 +16,7 @@
 
 package android.service.euicc;
 
-import android.service.euicc.DownloadResult;
-
 /** @hide */
 oneway interface IDownloadSubscriptionCallback {
-    void onComplete(in DownloadResult result);
+    void onComplete(int result);
 }
\ No newline at end of file
diff --git a/core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl b/core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl
index aa70e76..c975f18 100644
--- a/core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl
+++ b/core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl
@@ -16,9 +16,7 @@
 
 package android.service.euicc;
 
-import android.service.euicc.EraseResult;
-
 /** @hide */
 oneway interface IEraseSubscriptionsCallback {
-    void onComplete(in EraseResult result);
+    void onComplete(int result);
 }
\ No newline at end of file
diff --git a/core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl b/core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl
index 970adcd..0f91a6b 100644
--- a/core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl
+++ b/core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl
@@ -16,9 +16,7 @@
 
 package android.service.euicc;
 
-import android.service.euicc.SwitchResult;
-
 /** @hide */
 oneway interface ISwitchToSubscriptionCallback {
-    void onComplete(in SwitchResult result);
+    void onComplete(int result);
 }
\ No newline at end of file
diff --git a/core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl b/core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl
index 439759d..6666933 100644
--- a/core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl
+++ b/core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl
@@ -16,9 +16,7 @@
 
 package android.service.euicc;
 
-import android.service.euicc.UpdateNicknameResult;
-
 /** @hide */
 oneway interface IUpdateSubscriptionNicknameCallback {
-    void onComplete(in UpdateNicknameResult result);
+    void onComplete(int result);
 }
\ No newline at end of file
diff --git a/core/java/android/service/euicc/SwitchResult.aidl b/core/java/android/service/euicc/SwitchResult.aidl
deleted file mode 100644
index eb706a5..0000000
--- a/core/java/android/service/euicc/SwitchResult.aidl
+++ /dev/null
@@ -1,19 +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.service.euicc;
-
-parcelable SwitchResult;
diff --git a/core/java/android/service/euicc/SwitchResult.java b/core/java/android/service/euicc/SwitchResult.java
deleted file mode 100644
index f5dc4d33..0000000
--- a/core/java/android/service/euicc/SwitchResult.java
+++ /dev/null
@@ -1,106 +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.service.euicc;
-
-import android.annotation.IntDef;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Result of a {@link EuiccService#onSwitchToSubscription} operation.
- * @hide
- *
- * TODO(b/35851809): Make this a SystemApi.
- */
-public final class SwitchResult implements Parcelable {
-
-    public static final Creator<SwitchResult> CREATOR = new Creator<SwitchResult>() {
-        @Override
-        public SwitchResult createFromParcel(Parcel in) {
-            return new SwitchResult(in);
-        }
-
-        @Override
-        public SwitchResult[] newArray(int size) {
-            return new SwitchResult[size];
-        }
-    };
-
-    /** @hide */
-    @IntDef({
-            RESULT_OK,
-            RESULT_GENERIC_ERROR,
-            RESULT_MUST_DEACTIVATE_SIM,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ResultCode {}
-
-    public static final int RESULT_OK = 0;
-    public static final int RESULT_GENERIC_ERROR = 1;
-    public static final int RESULT_MUST_DEACTIVATE_SIM = 2;
-
-    /** Result of the operation - one of the RESULT_* constants. */
-    public final @ResultCode int result;
-
-    /** Implementation-defined detailed error code in case of a failure not covered here. */
-    public final int detailedCode;
-
-    private SwitchResult(int result, int detailedCode) {
-        this.result = result;
-        this.detailedCode = detailedCode;
-    }
-
-    private SwitchResult(Parcel in) {
-        this.result = in.readInt();
-        this.detailedCode = in.readInt();
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(result);
-        dest.writeInt(detailedCode);
-    }
-
-    /** Return a result indicating that the switch was successful. */
-    public static SwitchResult success() {
-        return new SwitchResult(RESULT_OK, 0);
-    }
-
-    /**
-     * Return a result indicating that an active SIM must be deactivated to perform the operation.
-     */
-    public static SwitchResult mustDeactivateSim() {
-        return new SwitchResult(RESULT_MUST_DEACTIVATE_SIM, 0);
-    }
-
-    /**
-     * Return a result indicating that an error occurred for which no other more specific error
-     * code has been defined.
-     *
-     * @param detailedCode an implemenation-defined detailed error code for debugging purposes.
-     */
-    public static SwitchResult genericError(int detailedCode) {
-        return new SwitchResult(RESULT_GENERIC_ERROR, detailedCode);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/core/java/android/service/euicc/UpdateNicknameResult.aidl b/core/java/android/service/euicc/UpdateNicknameResult.aidl
deleted file mode 100644
index 08b8491..0000000
--- a/core/java/android/service/euicc/UpdateNicknameResult.aidl
+++ /dev/null
@@ -1,19 +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.service.euicc;
-
-parcelable UpdateNicknameResult;
diff --git a/core/java/android/service/euicc/UpdateNicknameResult.java b/core/java/android/service/euicc/UpdateNicknameResult.java
deleted file mode 100644
index d871fc88..0000000
--- a/core/java/android/service/euicc/UpdateNicknameResult.java
+++ /dev/null
@@ -1,98 +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.service.euicc;
-
-import android.annotation.IntDef;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Result of a {@link EuiccService#onUpdateSubscriptionNickname} operation.
- * @hide
- *
- * TODO(b/35851809): Make this a SystemApi.
- */
-public final class UpdateNicknameResult implements Parcelable {
-
-    public static final Creator<UpdateNicknameResult> CREATOR =
-            new Creator<UpdateNicknameResult>() {
-        @Override
-        public UpdateNicknameResult createFromParcel(Parcel in) {
-            return new UpdateNicknameResult(in);
-        }
-
-        @Override
-        public UpdateNicknameResult[] newArray(int size) {
-            return new UpdateNicknameResult[size];
-        }
-    };
-
-    /** @hide */
-    @IntDef({
-            RESULT_OK,
-            RESULT_GENERIC_ERROR,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ResultCode {}
-
-    public static final int RESULT_OK = 0;
-    public static final int RESULT_GENERIC_ERROR = 1;
-
-    /** Result of the operation - one of the RESULT_* constants. */
-    public final @ResultCode int result;
-
-    /** Implementation-defined detailed error code in case of a failure not covered here. */
-    public final int detailedCode;
-
-    private UpdateNicknameResult(int result, int detailedCode) {
-        this.result = result;
-        this.detailedCode = detailedCode;
-    }
-
-    private UpdateNicknameResult(Parcel in) {
-        this.result = in.readInt();
-        this.detailedCode = in.readInt();
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(result);
-        dest.writeInt(detailedCode);
-    }
-
-    /** Return a result indicating that the update was successful. */
-    public static UpdateNicknameResult success() {
-        return new UpdateNicknameResult(RESULT_OK, 0);
-    }
-
-    /**
-     * Return a result indicating that an error occurred for which no other more specific error
-     * code has been defined.
-     *
-     * @param detailedCode an implemenation-defined detailed error code for debugging purposes.
-     */
-    public static UpdateNicknameResult genericError(int detailedCode) {
-        return new UpdateNicknameResult(RESULT_GENERIC_ERROR, detailedCode);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 539278f..e4d3142 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -17,25 +17,19 @@
 package android.service.wallpaper;
 
 import android.annotation.Nullable;
-import android.app.WallpaperColors;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.util.MergedConfiguration;
-import android.view.WindowInsets;
-
-import com.android.internal.R;
-import com.android.internal.os.HandlerCaller;
-import com.android.internal.view.BaseIWindow;
-import com.android.internal.view.BaseSurfaceHolder;
-
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.app.Service;
+import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.os.Bundle;
@@ -44,6 +38,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.MergedConfiguration;
 import android.view.Display;
 import android.view.Gravity;
 import android.view.IWindowSession;
@@ -55,9 +50,14 @@
 import android.view.SurfaceHolder;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.view.BaseIWindow;
+import com.android.internal.view.BaseSurfaceHolder;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -548,6 +548,7 @@
         /**
          * Notifies the engine that wallpaper colors changed significantly.
          * This will trigger a {@link #onComputeWallpaperColors()} call.
+         * @hide
          */
         public void invalidateColors() {
             try {
@@ -562,8 +563,14 @@
          * Notifies the system about what colors the wallpaper is using.
          * You might return null if no color information is available at the moment. In that case
          * you might want to call {@link #invalidateColors()} in a near future.
+         * <p>
+         * The simplest way of creating A {@link android.app.WallpaperColors} object is by using
+         * {@link android.app.WallpaperColors#fromBitmap(Bitmap)} or
+         * {@link android.app.WallpaperColors#fromDrawable(Drawable)}, but you can also specify
+         * your main colors and dark text support explicitly using one of the constructors.
          *
-         * @return List of wallpaper colors and their weights.
+         * @return Wallpaper colors.
+         * @hide
          */
         public @Nullable WallpaperColors onComputeWallpaperColors() {
             return null;
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 31cece4..0a73949 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -62,6 +62,12 @@
     public static final int VIRTUAL_KEY_RELEASE = 7;
 
     /**
+     * The user has performed a selection/insertion handle move on text field.
+     * @hide
+     */
+    public static final int TEXT_HANDLE_MOVE = 8;
+
+    /**
      * This is a private constant.  Feel free to renumber as desired.
      * @hide
      */
diff --git a/core/java/android/view/IWallpaperVisibilityListener.aidl b/core/java/android/view/IWallpaperVisibilityListener.aidl
new file mode 100644
index 0000000..349f984
--- /dev/null
+++ b/core/java/android/view/IWallpaperVisibilityListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * Listener to be invoked when wallpaper visibility changes.
+ * {@hide}
+ */
+oneway interface IWallpaperVisibilityListener {
+    /**
+     * Method that will be invoked when wallpaper becomes visible or hidden.
+     * @param visible True if wallpaper is being displayed; false otherwise.
+     * @param displayId The id of the display where wallpaper visibility changed.
+     */
+    void onWallpaperVisibilityChanged(boolean visible, int displayId);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 2b73c14..e576a0f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -39,6 +39,7 @@
 import android.view.IOnKeyguardExitResult;
 import android.view.IPinnedStackListener;
 import android.view.IRotationWatcher;
+import android.view.IWallpaperVisibilityListener;
 import android.view.IWindowSession;
 import android.view.IWindowSessionCallback;
 import android.view.KeyEvent;
@@ -256,6 +257,19 @@
     Bitmap screenshotWallpaper();
 
     /**
+     * Registers a wallpaper visibility listener.
+     * @return Current visibility.
+     */
+    boolean registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+        int displayId);
+
+    /**
+     * Remove a visibility watcher that was added using registerWallpaperVisibilityListener.
+     */
+    void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+        int displayId);
+
+    /**
      * Used only for assist -- request a screenshot of the current application.
      */
     boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver);
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 8bb3fa9..4f9dbd5 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -52,7 +52,9 @@
 
     private static native long nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture)
             throws OutOfResourcesException;
+
     private static native long nativeCreateFromSurfaceControl(long surfaceControlNativeObject);
+    private static native long nativeGetFromSurfaceControl(long surfaceControlNativeObject);
 
     private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty)
             throws OutOfResourcesException;
@@ -410,6 +412,9 @@
      * back from a client, converting it from the representation being managed
      * by the window manager to the representation the client uses to draw
      * in to it.
+     *
+     * @param other {@link SurfaceControl} to copy from.
+     *
      * @hide
      */
     public void copyFrom(SurfaceControl other) {
@@ -420,7 +425,39 @@
         long surfaceControlPtr = other.mNativeObject;
         if (surfaceControlPtr == 0) {
             throw new NullPointerException(
-                    "SurfaceControl native object is null. Are you using a released SurfaceControl?");
+                    "null SurfaceControl native object. Are you using a released SurfaceControl?");
+        }
+        long newNativeObject = nativeGetFromSurfaceControl(surfaceControlPtr);
+
+        synchronized (mLock) {
+            if (mNativeObject != 0) {
+                nativeRelease(mNativeObject);
+            }
+            setNativeObjectLocked(newNativeObject);
+        }
+    }
+
+    /**
+     * Gets a reference a surface created from this one.  This surface now holds a reference
+     * to the same data as the original surface, and is -not- the owner.
+     * This is for use by the window manager when returning a window surface
+     * back from a client, converting it from the representation being managed
+     * by the window manager to the representation the client uses to draw
+     * in to it.
+     *
+     * @param other {@link SurfaceControl} to create surface from.
+     *
+     * @hide
+     */
+    public void createFrom(SurfaceControl other) {
+        if (other == null) {
+            throw new IllegalArgumentException("other must not be null");
+        }
+
+        long surfaceControlPtr = other.mNativeObject;
+        if (surfaceControlPtr == 0) {
+            throw new NullPointerException(
+                    "null SurfaceControl native object. Are you using a released SurfaceControl?");
         }
         long newNativeObject = nativeCreateFromSurfaceControl(surfaceControlPtr);
 
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 679a9cd..34ceeb7 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -641,6 +641,16 @@
                         mSurface.copyFrom(mSurfaceControl);
                     }
 
+                    if (getContext().getApplicationInfo().targetSdkVersion
+                            < Build.VERSION_CODES.O) {
+                        // Some legacy applications use the underlying native {@link Surface} object
+                        // as a key to whether anything has changed. In these cases, updates to the
+                        // existing {@link Surface} will be ignored when the size changes.
+                        // Therefore, we must explicitly recreate the {@link Surface} in these
+                        // cases.
+                        mSurface.createFrom(mSurfaceControl);
+                    }
+
                     if (visible && mSurface.isValid()) {
                         if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
                             mSurfaceCreated = true;
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index de4d03e..489de56 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -996,6 +996,9 @@
         observer.mNative = null;
     }
 
+    /** Not actually public - internal use only. This doc to make lint happy */
+    public static native void disableVsync();
+
     static native void setupShadersDiskCache(String cacheFile);
 
     private static native void nRotateProcessStatsBuffer();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 97f5331..5b96454 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -802,7 +802,7 @@
      *
      * {@hide}
      */
-    public static final int LAST_APP_ACCESSIBILITY_ID = Integer.MAX_VALUE / 2;
+    public static final int LAST_APP_AUTOFILL_ID = Integer.MAX_VALUE / 2;
 
     /**
      * Attribute to find the autofilled highlight
@@ -2045,6 +2045,11 @@
     private SparseArray<Object> mKeyedTags;
 
     /**
+     * The next available accessibility id.
+     */
+    private static int sNextAccessibilityViewId;
+
+    /**
      * The animation currently associated with this view.
      * @hide
      */
@@ -2086,16 +2091,19 @@
     @ViewDebug.ExportedProperty(resolveId = true)
     int mID = NO_ID;
 
-    /** The ID of this view for accessibility and autofill purposes.
+    /** The ID of this view for autofill purposes.
      * <ul>
      *     <li>== {@link #NO_ID}: ID has not been assigned yet
-     *     <li>&le; {@link #LAST_APP_ACCESSIBILITY_ID}: View is not part of a activity. The ID is
+     *     <li>&le; {@link #LAST_APP_AUTOFILL_ID}: View is not part of a activity. The ID is
      *                                                  unique in the process. This might change
      *                                                  over activity lifecycle events.
-     *     <li>&gt; {@link #LAST_APP_ACCESSIBILITY_ID}: View is part of a activity. The ID is
+     *     <li>&gt; {@link #LAST_APP_AUTOFILL_ID}: View is part of a activity. The ID is
      *                                                  unique in the activity. This stays the same
      *                                                  over activity lifecycle events.
      */
+    private int mAutofillViewId = NO_ID;
+
+    // ID for accessibility purposes. This ID must be unique for every window
     private int mAccessibilityViewId = NO_ID;
 
     private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
@@ -7721,7 +7729,7 @@
         if (mAutofillId == null) {
             // The autofill id needs to be unique, but its value doesn't matter,
             // so it's better to reuse the accessibility id to save space.
-            mAutofillId = new AutofillId(getAccessibilityViewId());
+            mAutofillId = new AutofillId(getAutofillViewId());
         }
         return mAutofillId;
     }
@@ -7954,7 +7962,7 @@
 
     private boolean isAutofillable() {
         return getAutofillType() != AUTOFILL_TYPE_NONE && isImportantForAutofill()
-                && getAccessibilityViewId() > LAST_APP_ACCESSIBILITY_ID;
+                && getAutofillViewId() > LAST_APP_AUTOFILL_ID;
     }
 
     private void populateVirtualStructure(ViewStructure structure,
@@ -8472,12 +8480,26 @@
      */
     public int getAccessibilityViewId() {
         if (mAccessibilityViewId == NO_ID) {
-            mAccessibilityViewId = mContext.getNextAccessibilityId();
+            mAccessibilityViewId = sNextAccessibilityViewId++;
         }
         return mAccessibilityViewId;
     }
 
     /**
+     * Gets the unique identifier of this view on the screen for autofill purposes.
+     *
+     * @return The view autofill id.
+     *
+     * @hide
+     */
+    public int getAutofillViewId() {
+        if (mAutofillViewId == NO_ID) {
+            mAutofillViewId = mContext.getNextAutofillId();
+        }
+        return mAutofillViewId;
+    }
+
+    /**
      * Gets the unique identifier of the window in which this View reseides.
      *
      * @return The window accessibility id.
@@ -12125,7 +12147,7 @@
         if (isAutofillable()) {
             AutofillManager afm = getAutofillManager();
 
-            if (afm != null && getAccessibilityViewId() > LAST_APP_ACCESSIBILITY_ID) {
+            if (afm != null && getAutofillViewId() > LAST_APP_AUTOFILL_ID) {
                 if (mVisibilityChangeForAutofillHandler != null) {
                     mVisibilityChangeForAutofillHandler.removeMessages(0);
                 }
@@ -17555,16 +17577,16 @@
      *
      * @return Returns a Parcelable object containing the view's current dynamic
      *         state, or null if there is nothing interesting to save.
-     * @see #onRestoreInstanceState(android.os.Parcelable)
-     * @see #saveHierarchyState(android.util.SparseArray)
-     * @see #dispatchSaveInstanceState(android.util.SparseArray)
+     * @see #onRestoreInstanceState(Parcelable)
+     * @see #saveHierarchyState(SparseArray)
+     * @see #dispatchSaveInstanceState(SparseArray)
      * @see #setSaveEnabled(boolean)
      */
     @CallSuper
     @Nullable protected Parcelable onSaveInstanceState() {
         mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
         if (mStartActivityRequestWho != null || isAutofilled()
-                || mAccessibilityViewId > LAST_APP_ACCESSIBILITY_ID) {
+                || mAutofillViewId > LAST_APP_AUTOFILL_ID) {
             BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
 
             if (mStartActivityRequestWho != null) {
@@ -17575,13 +17597,13 @@
                 state.mSavedData |= BaseSavedState.IS_AUTOFILLED;
             }
 
-            if (mAccessibilityViewId > LAST_APP_ACCESSIBILITY_ID) {
-                state.mSavedData |= BaseSavedState.ACCESSIBILITY_ID;
+            if (mAutofillViewId > LAST_APP_AUTOFILL_ID) {
+                state.mSavedData |= BaseSavedState.AUTOFILL_ID;
             }
 
             state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
             state.mIsAutofilled = isAutofilled();
-            state.mAccessibilityViewId = mAccessibilityViewId;
+            state.mAutofillViewId = mAutofillViewId;
             return state;
         }
         return BaseSavedState.EMPTY_STATE;
@@ -17659,8 +17681,8 @@
             if ((baseState.mSavedData & BaseSavedState.IS_AUTOFILLED) != 0) {
                 setAutofilled(baseState.mIsAutofilled);
             }
-            if ((baseState.mSavedData & BaseSavedState.ACCESSIBILITY_ID) != 0) {
-                mAccessibilityViewId = baseState.mAccessibilityViewId;
+            if ((baseState.mSavedData & BaseSavedState.AUTOFILL_ID) != 0) {
+                mAutofillViewId = baseState.mAutofillViewId;
             }
         }
     }
@@ -21484,7 +21506,7 @@
      * @param accessibilityId The searched accessibility id.
      * @return The found view.
      */
-    final <T extends View> T  findViewByAccessibilityId(int accessibilityId) {
+    final <T extends View> T findViewByAccessibilityId(int accessibilityId) {
         if (accessibilityId < 0) {
             return null;
         }
@@ -21496,11 +21518,11 @@
     }
 
     /**
-     * Performs the traversal to find a view by its unuque and stable accessibility id.
+     * Performs the traversal to find a view by its unique and stable accessibility id.
      *
      * <strong>Note:</strong>This method does not stop at the root namespace
      * boundary since the user can touch the screen at an arbitrary location
-     * potentially crossing the root namespace bounday which will send an
+     * potentially crossing the root namespace boundary which will send an
      * accessibility event to accessibility services and they should be able
      * to obtain the event source. Also accessibility ids are guaranteed to be
      * unique in the window.
@@ -21517,6 +21539,23 @@
     }
 
     /**
+     * Performs the traversal to find a view by its autofill id.
+     *
+     * <strong>Note:</strong>This method does not stop at the root namespace
+     * boundary.
+     *
+     * @param autofillId The autofill id.
+     * @return The found view.
+     * @hide
+     */
+    public <T extends View> T findViewByAutofillIdTraversal(int autofillId) {
+        if (getAutofillViewId() == autofillId) {
+            return (T) this;
+        }
+        return null;
+    }
+
+    /**
      * Look for a child view with the given tag.  If this view has the given
      * tag, return this view.
      *
@@ -24982,13 +25021,13 @@
     public static class BaseSavedState extends AbsSavedState {
         static final int START_ACTIVITY_REQUESTED_WHO_SAVED = 0b1;
         static final int IS_AUTOFILLED = 0b10;
-        static final int ACCESSIBILITY_ID = 0b100;
+        static final int AUTOFILL_ID = 0b100;
 
         // Flags that describe what data in this state is valid
         int mSavedData;
         String mStartActivityRequestWhoSaved;
         boolean mIsAutofilled;
-        int mAccessibilityViewId;
+        int mAutofillViewId;
 
         /**
          * Constructor used when reading from a parcel. Reads the state of the superclass.
@@ -25011,7 +25050,7 @@
             mSavedData = source.readInt();
             mStartActivityRequestWhoSaved = source.readString();
             mIsAutofilled = source.readBoolean();
-            mAccessibilityViewId = source.readInt();
+            mAutofillViewId = source.readInt();
         }
 
         /**
@@ -25030,7 +25069,7 @@
             out.writeInt(mSavedData);
             out.writeString(mStartActivityRequestWhoSaved);
             out.writeBoolean(mIsAutofilled);
-            out.writeInt(mAccessibilityViewId);
+            out.writeInt(mAutofillViewId);
         }
 
         public static final Parcelable.Creator<BaseSavedState> CREATOR
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 2e817eb..cb92a4c 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1361,6 +1361,27 @@
         return null;
     }
 
+    /** @hide */
+    @Override
+    public View findViewByAutofillIdTraversal(int autofillId) {
+        View foundView = super.findViewByAutofillIdTraversal(autofillId);
+        if (foundView != null) {
+            return foundView;
+        }
+
+        final int childrenCount = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < childrenCount; i++) {
+            View child = children[i];
+            foundView = child.findViewByAutofillIdTraversal(autofillId);
+            if (foundView != null) {
+                return foundView;
+            }
+        }
+
+        return null;
+    }
+
     @Override
     public void dispatchWindowFocusChanged(boolean hasFocus) {
         super.dispatchWindowFocusChanged(hasFocus);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 4a231ef..04da4f1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2476,6 +2476,9 @@
         mInLayout = true;
 
         final View host = mView;
+        if (host == null) {
+            return;
+        }
         if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
             Log.v(mTag, "Laying out " + host + " to (" +
                     host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
@@ -2783,6 +2786,8 @@
     private void performDraw() {
         if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
             return;
+        } else if (mView == null) {
+            return;
         }
 
         final boolean fullRedrawNeeded = mFullRedrawNeeded;
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index dfbf962..4c0a190 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1391,6 +1391,13 @@
         public static final int PRIVATE_FLAG_TASK_SNAPSHOT = 0x00080000;
 
         /**
+         * Flag to indicate that this window should be ignored when determining what parts of the
+         * screen can be magnified.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT = 0x00100000;
+
+        /**
          * Control flags that are private to the platform.
          * @hide
          */
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 3a3e171..69892d9 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -971,14 +971,14 @@
     }
 
     /**
-     * Notifies that the availability of the accessibility button in the system's navigation area
+     * Notifies that the visibility of the accessibility button in the system's navigation area
      * has changed.
      *
-     * @param available {@code true} if the accessibility button is available within the system
+     * @param shown {@code true} if the accessibility button is visible within the system
      *                  navigation area, {@code false} otherwise
      * @hide
      */
-    public void notifyAccessibilityButtonAvailabilityChanged(boolean available) {
+    public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
         final IAccessibilityManager service;
         synchronized (mLock) {
             service = getServiceLocked();
@@ -987,9 +987,9 @@
             }
         }
         try {
-            service.notifyAccessibilityButtonAvailabilityChanged(available);
+            service.notifyAccessibilityButtonVisibilityChanged(shown);
         } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while dispatching accessibility button availability change", re);
+            Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re);
         }
     }
 
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 06cb5dc..3f499ab 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -64,7 +64,7 @@
 
     void notifyAccessibilityButtonClicked();
 
-    void notifyAccessibilityButtonAvailabilityChanged(boolean available);
+    void notifyAccessibilityButtonVisibilityChanged(boolean available);
 
     // Requires WRITE_SECURE_SETTINGS
     void performAccessibilityShortcut();
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 310ec1c..5b04f41 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -246,20 +246,20 @@
         /**
          * Finds views by traversing the hierarchies of the client.
          *
-         * @param viewIds The accessibility ids of the views to find
+         * @param viewIds The autofill ids of the views to find
          *
          * @return And array containing the views (empty if no views found).
          */
-        @NonNull View[] findViewsByAccessibilityIdTraversal(@NonNull int[] viewIds);
+        @NonNull View[] findViewsByAutofillIdTraversal(@NonNull int[] viewIds);
 
         /**
          * Finds a view by traversing the hierarchies of the client.
          *
-         * @param viewId The accessibility id of the views to find
+         * @param viewId The autofill id of the views to find
          *
          * @return The view, or {@code null} if not found
          */
-        @Nullable View findViewByAccessibilityIdTraversal(int viewId);
+        @Nullable View findViewByAutofillIdTraversal(int viewId);
 
         /**
          * Runs the specified action on the UI thread.
@@ -795,11 +795,11 @@
     }
 
     private static AutofillId getAutofillId(View view) {
-        return new AutofillId(view.getAccessibilityViewId());
+        return new AutofillId(view.getAutofillViewId());
     }
 
     private static AutofillId getAutofillId(View parent, int virtualId) {
-        return new AutofillId(parent.getAccessibilityViewId(), virtualId);
+        return new AutofillId(parent.getAutofillViewId(), virtualId);
     }
 
     private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
@@ -1039,7 +1039,7 @@
             final int itemCount = ids.size();
             int numApplied = 0;
             ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
-            final View[] views = client.findViewsByAccessibilityIdTraversal(getViewIds(ids));
+            final View[] views = client.findViewsByAutofillIdTraversal(getViewIds(ids));
 
             for (int i = 0; i < itemCount; i++) {
                 final AutofillId id = ids.get(i);
@@ -1232,7 +1232,7 @@
             return null;
         }
 
-        return client.findViewByAccessibilityIdTraversal(autofillId.getViewId());
+        return client.findViewByAutofillIdTraversal(autofillId.getViewId());
     }
 
     /** @hide */
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index 1e8207a..f712d5f 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -293,9 +293,19 @@
      * @param setSelected whether to set the specified day as selected
      */
     private void setDate(long timeInMillis, boolean animate, boolean setSelected) {
+        boolean dateClamped = false;
+        // Clamp the target day in milliseconds to the min or max if outside the range.
+        if (timeInMillis < mMinDate.getTimeInMillis()) {
+            timeInMillis = mMinDate.getTimeInMillis();
+            dateClamped = true;
+        } else if (timeInMillis > mMaxDate.getTimeInMillis()) {
+            timeInMillis = mMaxDate.getTimeInMillis();
+            dateClamped = true;
+        }
+
         getTempCalendarForTime(timeInMillis);
 
-        if (setSelected) {
+        if (setSelected || dateClamped) {
             mSelectedDay.setTimeInMillis(timeInMillis);
         }
 
@@ -353,13 +363,6 @@
     public void onRangeChanged() {
         mAdapter.setRange(mMinDate, mMaxDate);
 
-        // Clamp the selected day to the new min/max.
-        if (mSelectedDay.before(mMinDate)) {
-            mSelectedDay.setTimeInMillis(mMinDate.getTimeInMillis());
-        } else if (mSelectedDay.after(mMaxDate)) {
-            mSelectedDay.setTimeInMillis(mMaxDate.getTimeInMillis());
-        }
-
         // Changing the min/max date changes the selection position since we
         // don't really have stable IDs. Jumps immediately to the new position.
         setDate(mSelectedDay.getTimeInMillis(), false, false);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 0e6e3ae..45e5f8a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2584,14 +2584,18 @@
         if (offset == -1) {
             return;
         }
+
         stopTextActionModeWithPreservingSelection();
-        final boolean isOnSelection = mTextView.hasSelection()
-                && offset >= mTextView.getSelectionStart() && offset <= mTextView.getSelectionEnd();
-        if (!isOnSelection) {
-            // Right clicked position is not on the selection. Remove the selection and move the
-            // cursor to the right clicked position.
-            Selection.setSelection((Spannable) mTextView.getText(), offset);
-            stopTextActionMode();
+        if (mTextView.canSelectText()) {
+            final boolean isOnSelection = mTextView.hasSelection()
+                    && offset >= mTextView.getSelectionStart()
+                    && offset <= mTextView.getSelectionEnd();
+            if (!isOnSelection) {
+                // Right clicked position is not on the selection. Remove the selection and move the
+                // cursor to the right clicked position.
+                Selection.setSelection((Spannable) mTextView.getText(), offset);
+                stopTextActionMode();
+            }
         }
 
         if (shouldOfferToShowSuggestions()) {
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index f4c18c3..1bee692 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -16,6 +16,7 @@
 
 package com.android.internal.app;
 
+import android.app.PendingIntent;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.os.ParcelUuid;
@@ -26,7 +27,6 @@
  */
 interface ISoundTriggerService {
 
-
     SoundTrigger.GenericSoundModel getSoundModel(in ParcelUuid soundModelId);
 
     void updateSoundModel(in SoundTrigger.GenericSoundModel soundModel);
@@ -36,8 +36,17 @@
     int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback,
          in SoundTrigger.RecognitionConfig config);
 
-    /**
-     * Stops recognition.
-     */
     int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
+
+    int loadGenericSoundModel(in SoundTrigger.GenericSoundModel soundModel);
+    int loadKeyphraseSoundModel(in SoundTrigger.KeyphraseSoundModel soundModel);
+
+    int startRecognitionForIntent(in ParcelUuid soundModelId, in PendingIntent callbackIntent,
+         in SoundTrigger.RecognitionConfig config);
+
+    int stopRecognitionForIntent(in ParcelUuid soundModelId);
+
+    int unloadSoundModel(in ParcelUuid soundModelId);
+
+    boolean isRecognitionActive(in ParcelUuid parcelUuid);
 }
diff --git a/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java b/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java
index 56d60a1..9ac753b 100644
--- a/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java
+++ b/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java
@@ -61,7 +61,7 @@
  * This means that the color space is divided into distinct colors, rather than representative
  * colors.
  */
-final class ColorCutQuantizer {
+final class ColorCutQuantizer implements Quantizer {
 
     private static final String LOG_TAG = "ColorCutQuantizer";
     private static final boolean LOG_TIMINGS = false;
@@ -73,22 +73,22 @@
     private static final int QUANTIZE_WORD_WIDTH = 5;
     private static final int QUANTIZE_WORD_MASK = (1 << QUANTIZE_WORD_WIDTH) - 1;
 
-    final int[] mColors;
-    final int[] mHistogram;
-    final List<Swatch> mQuantizedColors;
-    final TimingLogger mTimingLogger;
-    final Palette.Filter[] mFilters;
+    int[] mColors;
+    int[] mHistogram;
+    List<Swatch> mQuantizedColors;
+    TimingLogger mTimingLogger;
+    Palette.Filter[] mFilters;
 
     private final float[] mTempHsl = new float[3];
 
     /**
-     * Constructor.
+     * Execute color quantization.
      *
      * @param pixels histogram representing an image's pixel data
      * @param maxColors The maximum number of colors that should be in the result palette.
      * @param filters Set of filters to use in the quantization stage
      */
-    ColorCutQuantizer(final int[] pixels, final int maxColors, final Palette.Filter[] filters) {
+    public void quantize(final int[] pixels, final int maxColors, final Palette.Filter[] filters) {
         mTimingLogger = LOG_TIMINGS ? new TimingLogger(LOG_TAG, "Creation") : null;
         mFilters = filters;
 
@@ -160,7 +160,7 @@
     /**
      * @return the list of quantized colors
      */
-    List<Swatch> getQuantizedColors() {
+    public List<Swatch> getQuantizedColors() {
         return mQuantizedColors;
     }
 
diff --git a/core/java/com/android/internal/graphics/palette/Palette.java b/core/java/com/android/internal/graphics/palette/Palette.java
index 9f1504a..a4f9a59 100644
--- a/core/java/com/android/internal/graphics/palette/Palette.java
+++ b/core/java/com/android/internal/graphics/palette/Palette.java
@@ -613,6 +613,8 @@
         private final List<Palette.Filter> mFilters = new ArrayList<>();
         private Rect mRegion;
 
+        private Quantizer mQuantizer;
+
         /**
          * Construct a new {@link Palette.Builder} using a source {@link Bitmap}
          */
@@ -726,6 +728,18 @@
         }
 
         /**
+         * Set a specific quantization algorithm. {@link ColorCutQuantizer} will
+         * be used if unspecified.
+         *
+         * @param quantizer Quantizer implementation.
+         */
+        @NonNull
+        public Palette.Builder setQuantizer(Quantizer quantizer) {
+            mQuantizer = quantizer;
+            return this;
+        }
+
+        /**
          * Set a region of the bitmap to be used exclusively when calculating the palette.
          * <p>This only works when the original input is a {@link Bitmap}.</p>
          *
@@ -818,17 +832,19 @@
                 }
 
                 // Now generate a quantizer from the Bitmap
-                final ColorCutQuantizer quantizer = new ColorCutQuantizer(
-                        getPixelsFromBitmap(bitmap),
-                        mMaxColors,
-                        mFilters.isEmpty() ? null : mFilters.toArray(new Palette.Filter[mFilters.size()]));
+                if (mQuantizer == null) {
+                    mQuantizer = new ColorCutQuantizer();
+                }
+                mQuantizer.quantize(getPixelsFromBitmap(bitmap),
+                            mMaxColors, mFilters.isEmpty() ? null :
+                            mFilters.toArray(new Palette.Filter[mFilters.size()]));
 
                 // If created a new bitmap, recycle it
                 if (bitmap != mBitmap) {
                     bitmap.recycle();
                 }
 
-                swatches = quantizer.getQuantizedColors();
+                swatches = mQuantizer.getQuantizedColors();
 
                 if (logger != null) {
                     logger.addSplit("Color quantization completed");
diff --git a/core/java/android/service/euicc/EraseResult.aidl b/core/java/com/android/internal/graphics/palette/Quantizer.java
similarity index 61%
copy from core/java/android/service/euicc/EraseResult.aidl
copy to core/java/com/android/internal/graphics/palette/Quantizer.java
index e28a097..db60f2e 100644
--- a/core/java/android/service/euicc/EraseResult.aidl
+++ b/core/java/com/android/internal/graphics/palette/Quantizer.java
@@ -11,9 +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.service.euicc;
+package com.android.internal.graphics.palette;
 
-parcelable EraseResult;
+import java.util.List;
+
+/**
+ * Definition of an algorithm that receives pixels and outputs a list of colors.
+ */
+public interface Quantizer {
+    void quantize(final int[] pixels, final int maxColors, final Palette.Filter[] filters);
+    List<Palette.Swatch> getQuantizedColors();
+}
diff --git a/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java b/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java
new file mode 100644
index 0000000..b035535
--- /dev/null
+++ b/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java
@@ -0,0 +1,154 @@
+/*
+ * 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.internal.graphics.palette;
+
+import android.util.Log;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.ml.clustering.KMeans;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * A quantizer that uses k-means
+ */
+public class VariationalKMeansQuantizer implements Quantizer {
+
+    private static final String TAG = "KMeansQuantizer";
+    private static final boolean DEBUG = false;
+
+    /**
+     * Clusters closer than this value will me merged.
+     */
+    private final float mMinClusterSqDistance;
+
+    /**
+     * K-means can get stuck in local optima, this can be avoided by
+     * repeating it and getting the "best" execution.
+     */
+    private final int mInitializations;
+
+    /**
+     * Initialize KMeans with a fixed random state to have
+     * consistent results across multiple runs.
+     */
+    private final KMeans mKMeans = new KMeans(new Random(0), 30, 0);
+
+    private List<Palette.Swatch> mQuantizedColors;
+
+    public VariationalKMeansQuantizer() {
+        this(0.25f /* cluster distance */);
+    }
+
+    public VariationalKMeansQuantizer(float minClusterDistance) {
+        this(minClusterDistance, 1 /* initializations */);
+    }
+
+    public VariationalKMeansQuantizer(float minClusterDistance, int initializations) {
+        mMinClusterSqDistance = minClusterDistance * minClusterDistance;
+        mInitializations = initializations;
+    }
+
+    /**
+     * K-Means quantizer.
+     *
+     * @param pixels Pixels to quantize.
+     * @param maxColors Maximum number of clusters to extract.
+     * @param filters Colors that should be ignored
+     */
+    @Override
+    public void quantize(int[] pixels, int maxColors, Palette.Filter[] filters) {
+        // Start by converting all colors to HSL.
+        // HLS is way more meaningful for clustering than RGB.
+        final float[] hsl = {0, 0, 0};
+        final float[][] hslPixels = new float[pixels.length][3];
+        for (int i = 0; i < pixels.length; i++) {
+            ColorUtils.colorToHSL(pixels[i], hsl);
+            // Normalize hue so all values go from 0 to 1.
+            hslPixels[i][0] = hsl[0] / 360f;
+            hslPixels[i][1] = hsl[1];
+            hslPixels[i][2] = hsl[2];
+        }
+
+        final List<KMeans.Mean> optimalMeans = getOptimalKMeans(maxColors, hslPixels);
+
+        // Ideally we should run k-means again to merge clusters but it would be too expensive,
+        // instead we just merge all clusters that are closer than a threshold.
+        for (int i = 0; i < optimalMeans.size(); i++) {
+            KMeans.Mean current = optimalMeans.get(i);
+            float[] currentCentroid = current.getCentroid();
+            for (int j = i + 1; j < optimalMeans.size(); j++) {
+                KMeans.Mean compareTo = optimalMeans.get(j);
+                float[] compareToCentroid = compareTo.getCentroid();
+                float sqDistance = KMeans.sqDistance(currentCentroid, compareToCentroid);
+                // Merge them
+                if (sqDistance < mMinClusterSqDistance) {
+                    optimalMeans.remove(compareTo);
+                    current.getItems().addAll(compareTo.getItems());
+                    for (int k = 0; k < currentCentroid.length; k++) {
+                        currentCentroid[k] += (compareToCentroid[k] - currentCentroid[k]) / 2.0;
+                    }
+                    j--;
+                }
+            }
+        }
+
+        // Convert data to final format, de-normalizing the hue.
+        mQuantizedColors = new ArrayList<>();
+        for (KMeans.Mean mean : optimalMeans) {
+            if (mean.getItems().size() == 0) {
+                continue;
+            }
+            float[] centroid = mean.getCentroid();
+            mQuantizedColors.add(new Palette.Swatch(new float[]{
+                    centroid[0] * 360f,
+                    centroid[1],
+                    centroid[2]
+            }, mean.getItems().size()));
+        }
+    }
+
+    private List<KMeans.Mean> getOptimalKMeans(int k, float[][] inputData) {
+        List<KMeans.Mean> optimal = null;
+        double optimalScore = -Double.MAX_VALUE;
+        int runs = mInitializations;
+        while (runs > 0) {
+            if (DEBUG) {
+                Log.d(TAG, "k-means run: " + runs);
+            }
+            List<KMeans.Mean> means = mKMeans.predict(k, inputData);
+            double score = KMeans.score(means);
+            if (optimal == null || score > optimalScore) {
+                if (DEBUG) {
+                    Log.d(TAG, "\tnew optimal score: " + score);
+                }
+                optimalScore = score;
+                optimal = means;
+            }
+            runs--;
+        }
+
+        return optimal;
+    }
+
+    @Override
+    public List<Palette.Swatch> getQuantizedColors() {
+        return mQuantizedColors;
+    }
+}
diff --git a/core/java/com/android/internal/ml/clustering/KMeans.java b/core/java/com/android/internal/ml/clustering/KMeans.java
new file mode 100644
index 0000000..4d5b333
--- /dev/null
+++ b/core/java/com/android/internal/ml/clustering/KMeans.java
@@ -0,0 +1,243 @@
+/*
+ * 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.internal.ml.clustering;
+
+import android.annotation.NonNull;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Simple K-Means implementation
+ */
+public class KMeans {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "KMeans";
+    private final Random mRandomState;
+    private final int mMaxIterations;
+    private float mSqConvergenceEpsilon;
+
+    public KMeans() {
+        this(new Random());
+    }
+
+    public KMeans(Random random) {
+        this(random, 30 /* maxIterations */, 0.005f /* convergenceEpsilon */);
+    }
+    public KMeans(Random random, int maxIterations, float convergenceEpsilon) {
+        mRandomState = random;
+        mMaxIterations = maxIterations;
+        mSqConvergenceEpsilon = convergenceEpsilon * convergenceEpsilon;
+    }
+
+    /**
+     * Runs k-means on the input data (X) trying to find k means.
+     *
+     * K-Means is known for getting stuck into local optima, so you might
+     * want to run it multiple time and argmax on {@link KMeans#score(List)}
+     *
+     * @param k The number of points to return.
+     * @param inputData Input data.
+     * @return An array of k Means, each representing a centroid and data points that belong to it.
+     */
+    public List<Mean> predict(final int k, final float[][] inputData) {
+        checkDataSetSanity(inputData);
+        int dimension = inputData[0].length;
+
+        final ArrayList<Mean> means = new ArrayList<>();
+        for (int i = 0; i < k; i++) {
+            Mean m = new Mean(dimension);
+            for (int j = 0; j < dimension; j++) {
+                m.mCentroid[j] = mRandomState.nextFloat();
+            }
+            means.add(m);
+        }
+
+        // Iterate until we converge or run out of iterations
+        boolean converged = false;
+        for (int i = 0; i < mMaxIterations; i++) {
+            converged = step(means, inputData);
+            if (converged) {
+                if (DEBUG) Log.d(TAG, "Converged at iteration: " + i);
+                break;
+            }
+        }
+        if (!converged && DEBUG) Log.d(TAG, "Did not converge");
+
+        return means;
+    }
+
+    /**
+     * Score calculates the inertia between means.
+     * This can be considered as an E step of an EM algorithm.
+     *
+     * @param means Means to use when calculating score.
+     * @return The score
+     */
+    public static double score(@NonNull List<Mean> means) {
+        double score = 0;
+        final int meansSize = means.size();
+        for (int i = 0; i < meansSize; i++) {
+            Mean mean = means.get(i);
+            for (int j = 0; j < meansSize; j++) {
+                Mean compareTo = means.get(j);
+                if (mean == compareTo) {
+                    continue;
+                }
+                double distance = Math.sqrt(sqDistance(mean.mCentroid, compareTo.mCentroid));
+                score += distance;
+            }
+        }
+        return score;
+    }
+
+    @VisibleForTesting
+    public void checkDataSetSanity(float[][] inputData) {
+        if (inputData == null) {
+            throw new IllegalArgumentException("Data set is null.");
+        } else if (inputData.length == 0) {
+            throw new IllegalArgumentException("Data set is empty.");
+        } else if (inputData[0] == null) {
+            throw new IllegalArgumentException("Bad data set format.");
+        }
+
+        final int dimension = inputData[0].length;
+        final int length = inputData.length;
+        for (int i = 1; i < length; i++) {
+            if (inputData[i] == null || inputData[i].length != dimension) {
+                throw new IllegalArgumentException("Bad data set format.");
+            }
+        }
+    }
+
+    /**
+     * K-Means iteration.
+     *
+     * @param means Current means
+     * @param inputData Input data
+     * @return True if data set converged
+     */
+    private boolean step(final ArrayList<Mean> means, final float[][] inputData) {
+
+        // Clean up the previous state because we need to compute
+        // which point belongs to each mean again.
+        for (int i = means.size() - 1; i >= 0; i--) {
+            final Mean mean = means.get(i);
+            mean.mClosestItems.clear();
+        }
+        for (int i = inputData.length - 1; i >= 0; i--) {
+            final float[] current = inputData[i];
+            final Mean nearest = nearestMean(current, means);
+            nearest.mClosestItems.add(current);
+        }
+
+        boolean converged = true;
+        // Move each mean towards the nearest data set points
+        for (int i = means.size() - 1; i >= 0; i--) {
+            final Mean mean = means.get(i);
+            if (mean.mClosestItems.size() == 0) {
+                continue;
+            }
+
+            // Compute the new mean centroid:
+            //   1. Sum all all points
+            //   2. Average them
+            final float[] oldCentroid = mean.mCentroid;
+            mean.mCentroid = new float[oldCentroid.length];
+            for (int j = 0; j < mean.mClosestItems.size(); j++) {
+                // Update each centroid component
+                for (int p = 0; p < mean.mCentroid.length; p++) {
+                    mean.mCentroid[p] += mean.mClosestItems.get(j)[p];
+                }
+            }
+            for (int j = 0; j < mean.mCentroid.length; j++) {
+                mean.mCentroid[j] /= mean.mClosestItems.size();
+            }
+
+            // We converged if the centroid didn't move for any of the means.
+            if (sqDistance(oldCentroid, mean.mCentroid) > mSqConvergenceEpsilon) {
+                converged = false;
+            }
+        }
+        return converged;
+    }
+
+    @VisibleForTesting
+    public static Mean nearestMean(float[] point, List<Mean> means) {
+        Mean nearest = null;
+        float nearestDistance = Float.MAX_VALUE;
+
+        final int meanCount = means.size();
+        for (int i = 0; i < meanCount; i++) {
+            Mean next = means.get(i);
+            // We don't need the sqrt when comparing distances in euclidean space
+            // because they exist on both sides of the equation and cancel each other out.
+            float nextDistance = sqDistance(point, next.mCentroid);
+            if (nextDistance < nearestDistance) {
+                nearest = next;
+                nearestDistance = nextDistance;
+            }
+        }
+        return nearest;
+    }
+
+    @VisibleForTesting
+    public static float sqDistance(float[] a, float[] b) {
+        float dist = 0;
+        final int length = a.length;
+        for (int i = 0; i < length; i++) {
+            dist += (a[i] - b[i]) * (a[i] - b[i]);
+        }
+        return dist;
+    }
+
+    /**
+     * Definition of a mean, contains a centroid and points on its cluster.
+     */
+    public static class Mean {
+        float[] mCentroid;
+        final ArrayList<float[]> mClosestItems = new ArrayList<>();
+
+        public Mean(int dimension) {
+            mCentroid = new float[dimension];
+        }
+
+        public Mean(float ...centroid) {
+            mCentroid = centroid;
+        }
+
+        public float[] getCentroid() {
+            return mCentroid;
+        }
+
+        public List<float[]> getItems() {
+            return mClosestItems;
+        }
+
+        @Override
+        public String toString() {
+            return "Mean(centroid: " + Arrays.toString(mCentroid) + ", size: "
+                    + mClosestItems.size() + ")";
+        }
+    }
+}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 226e9e3..1779ada 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -1761,6 +1761,14 @@
     return nativeToJavaStatus(AudioSystem::systemReady());
 }
 
+static jfloat
+android_media_AudioSystem_getStreamVolumeDB(JNIEnv *env, jobject thiz,
+                                            jint stream, jint index, jint device)
+{
+    return (jfloat)AudioSystem::getStreamVolumeDB((audio_stream_type_t)stream,
+                                                  (int)index,
+                                                  (audio_devices_t)device);
+}
 
 // ----------------------------------------------------------------------------
 
@@ -1814,6 +1822,7 @@
     {"native_register_recording_callback", "()V",
                                     (void *)android_media_AudioSystem_registerRecordingCallback},
     {"systemReady", "()I", (void *)android_media_AudioSystem_systemReady},
+    {"getStreamVolumeDB", "(III)F", (void *)android_media_AudioSystem_getStreamVolumeDB},
 };
 
 
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index bde9134..494e266 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -400,6 +400,16 @@
 
 static jlong nativeCreateFromSurfaceControl(JNIEnv* env, jclass clazz,
         jlong surfaceControlNativeObj) {
+    sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj));
+    sp<Surface> surface(ctrl->createSurface());
+    if (surface != NULL) {
+        surface->incStrong(&sRefBaseOwner);
+    }
+    return reinterpret_cast<jlong>(surface.get());
+}
+
+static jlong nativeGetFromSurfaceControl(JNIEnv* env, jclass clazz,
+        jlong surfaceControlNativeObj) {
     /*
      * This is used by the WindowManagerService just after constructing
      * a Surface and is necessary for returning the Surface reference to
@@ -596,6 +606,8 @@
             (void*)nativeAllocateBuffers },
     {"nativeCreateFromSurfaceControl", "(J)J",
             (void*)nativeCreateFromSurfaceControl },
+    {"nativeGetFromSurfaceControl", "(J)J",
+            (void*)nativeGetFromSurfaceControl },
     {"nativeReadFromParcel", "(JLandroid/os/Parcel;)J",
             (void*)nativeReadFromParcel },
     {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 2657140..286c1a2 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -926,6 +926,10 @@
     return createBitmap(env, bitmap.release(), android::bitmap::kBitmapCreateFlag_Mutable);
 }
 
+static void android_view_ThreadedRenderer_disableVsync(JNIEnv*, jclass) {
+    RenderProxy::disableVsync();
+}
+
 // ----------------------------------------------------------------------------
 // FrameMetricsObserver
 // ----------------------------------------------------------------------------
@@ -1024,6 +1028,7 @@
                 (void*)android_view_ThreadedRenderer_copySurfaceInto },
     { "nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;",
             (void*)android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode },
+    { "disableVsync", "()V", (void*)android_view_ThreadedRenderer_disableVsync },
 };
 
 int register_android_view_ThreadedRenderer(JNIEnv* env) {
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index d509c12..aa80ba5 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -811,7 +811,7 @@
     <string name="js_dialog_before_unload_title" msgid="2619376555525116593">"Confirmació de la navegació"</string>
     <string name="js_dialog_before_unload_positive_button" msgid="3112752010600484130">"Surt d\'aquesta pàgina"</string>
     <string name="js_dialog_before_unload_negative_button" msgid="5614861293026099715">"Queda\'t en aquesta pàgina"</string>
-    <string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nEstàs segur que vols sortir d\'aquesta pàgina?"</string>
+    <string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nConfirmes que vols sortir d\'aquesta pàgina?"</string>
     <string name="save_password_label" msgid="6860261758665825069">"Confirma"</string>
     <string name="double_tap_toast" msgid="4595046515400268881">"Consell: Pica dos cops per ampliar i per reduir."</string>
     <string name="autofill_this_form" msgid="4616758841157816676">"Em. aut."</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index e64cdd2..eab7b5d 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1290,10 +1290,10 @@
     <string name="vpn_title_long" msgid="6400714798049252294">"VPN aktiveres af <xliff:g id="APP">%s</xliff:g>"</string>
     <string name="vpn_text" msgid="1610714069627824309">"Tryk for at administrere netværket."</string>
     <string name="vpn_text_long" msgid="4907843483284977618">"Forbundet til <xliff:g id="SESSION">%s</xliff:g>. Tryk for at administrere netværket."</string>
-    <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Opretter forbindelse til altid aktiveret VPN…"</string>
-    <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Altid aktiveret VPN er forbundet"</string>
-    <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Forbindelsen til altid aktiveret VPN er afbrudt"</string>
-    <string name="vpn_lockdown_error" msgid="6009249814034708175">"Fejl i altid aktiveret VPN"</string>
+    <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Opretter forbindelse til konstant VPN…"</string>
+    <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Konstant VPN er forbundet"</string>
+    <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Forbindelsen til konstant VPN er afbrudt"</string>
+    <string name="vpn_lockdown_error" msgid="6009249814034708175">"Fejl i konstant VPN"</string>
     <string name="vpn_lockdown_config" msgid="5099330695245008680">"Tryk for at konfigurere"</string>
     <string name="upload_file" msgid="2897957172366730416">"Vælg fil"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index b58df81..e72e419 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -447,7 +447,7 @@
     <string name="permlab_changeTetherState" msgid="5952584964373017960">"Tethering-Konnektivität ändern"</string>
     <string name="permdesc_changeTetherState" msgid="1524441344412319780">"Ermöglicht der App, den Status der Tethering-Konnektivität zu ändern"</string>
     <string name="permlab_accessWifiState" msgid="5202012949247040011">"WLAN-Verbindungen abrufen"</string>
-    <string name="permdesc_accessWifiState" msgid="5002798077387803726">"Ermöglicht der App, Informationen zu WLANs abzurufen, etwa ob ein WLAN aktiviert ist, und den Namen verbundener WLAN-Geräte."</string>
+    <string name="permdesc_accessWifiState" msgid="5002798077387803726">"Ermöglicht der App, Informationen zu WLAN-Netzwerken abzurufen, etwa ob ein WLAN aktiviert ist, und den Namen verbundener WLAN-Geräte."</string>
     <string name="permlab_changeWifiState" msgid="6550641188749128035">"WLAN-Verbindungen herstellen und trennen"</string>
     <string name="permdesc_changeWifiState" msgid="7137950297386127533">"Ermöglicht der App, eine Verbindung zu WLAN-Zugangspunkten herzustellen und solche zu trennen und Änderungen an der Gerätekonfiguration für WLAN-Netzwerke vorzunehmen."</string>
     <string name="permlab_changeWifiMulticastState" msgid="1368253871483254784">"WLAN-Multicast-Empfang zulassen"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 002997f5..97e85e1 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -90,7 +90,7 @@
     <string name="serviceNotProvisioned" msgid="8614830180508686666">"Η υπηρεσία δεν προβλέπεται."</string>
     <string name="CLIRPermanent" msgid="3377371145926835671">"Δεν μπορείτε να αλλάξετε τη ρύθμιση του αναγνωριστικού καλούντος."</string>
     <string name="RestrictedOnDataTitle" msgid="1322504692764166532">"Δεν υπάρχει υπηρεσία δεδομένων"</string>
-    <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"Αδυναμία πραγματοποίησης κλήσεων έκτακτης ανάγκης"</string>
+    <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"Δεν επιτρέπονται οι κλήσεις έκτακτης ανάγκης"</string>
     <string name="RestrictedOnNormalTitle" msgid="3179574012752700984">"Δεν υπάρχει φωνητική υπηρεσία"</string>
     <string name="RestrictedOnAllVoiceTitle" msgid="158800171499150681">"Δεν υπάρχει φωνητική υπηρεσία/υπηρεσία έκτακτης ανάγκης"</string>
     <string name="RestrictedStateContent" msgid="4278821484643362350">"Δεν προσφέρεται προσωρινά από το δίκτυο κινητής τηλεφωνίας στην τοποθεσία σας"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 52ffe6c..a02e8cc 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -90,7 +90,7 @@
     <string name="serviceNotProvisioned" msgid="8614830180508686666">"ਸੇਵਾ ਪ੍ਰਬੰਧਿਤ ਨਹੀਂ ਹੈ।"</string>
     <string name="CLIRPermanent" msgid="3377371145926835671">"ਤੁਸੀਂ ਕਾਲਰ ID ਸੈਟਿੰਗ ਨਹੀਂ ਬਦਲ ਸਕਦੇ।"</string>
     <string name="RestrictedOnDataTitle" msgid="1322504692764166532">"ਕੋਈ ਡੈਟਾ ਸੇਵਾ ਨਹੀਂ"</string>
-    <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"ਕੋਈ ਸੰਕਟਕਾਲੀਨ ਕਾਲਿੰਗ ਨਹੀਂ"</string>
+    <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"ਸੰਕਟਕਾਲ ਵਿੱਚ ਕੋਈ ਕਾਲ ਨਹੀਂ"</string>
     <string name="RestrictedOnNormalTitle" msgid="3179574012752700984">"ਕੋਈ ਆਵਾਜ਼ੀ ਸੇਵਾ ਨਹੀਂ"</string>
     <string name="RestrictedOnAllVoiceTitle" msgid="158800171499150681">"ਕੋਈ ਆਵਾਜ਼ੀ/ਸੰਕਟਕਾਲੀਨ ਸੇਵਾ ਨਹੀਂ"</string>
     <string name="RestrictedStateContent" msgid="4278821484643362350">"ਤੁਹਾਡੇ ਟਿਕਾਣੇ \'ਤੇ ਅਸਥਾਈ ਤੌਰ \'ਤੇ ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਵੱਲੋਂ ਉਪਲਬਧ ਨਹੀਂ ਕਰਵਾਈ ਗਈ"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 76dc06f..d1e15280 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -90,7 +90,7 @@
     <string name="serviceNotProvisioned" msgid="8614830180508686666">"సేవ కేటాయించబడలేదు."</string>
     <string name="CLIRPermanent" msgid="3377371145926835671">"మీరు కాలర్ ID సెట్టింగ్‌ను మార్చలేరు."</string>
     <string name="RestrictedOnDataTitle" msgid="1322504692764166532">"డేటా సేవ లేదు"</string>
-    <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"అత్యవసర కాలింగ్ లేదు"</string>
+    <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"అత్యవసర కాలింగ్ సదుపాయం లేదు"</string>
     <string name="RestrictedOnNormalTitle" msgid="3179574012752700984">"వాయిస్ సేవ లేదు"</string>
     <string name="RestrictedOnAllVoiceTitle" msgid="158800171499150681">"వాయిస్/అత్యవసర సేవ లేదు"</string>
     <string name="RestrictedStateContent" msgid="4278821484643362350">"మీ స్థానంలో మొబైల్ నెట్‌వర్క్ ద్వారా తాత్కాలికంగా అందించబడదు"</string>
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index d154730..df2b35b 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -32,7 +32,8 @@
 OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t height) {
     LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
 
-    OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height);
+    OffscreenBuffer* buffer = mRenderState.layerPool().get(
+            mRenderState, width, height, mWideColorGamut);
     startRepaintLayer(buffer, Rect(width, height));
     return buffer;
 }
@@ -103,7 +104,8 @@
 OffscreenBuffer* BakedOpRenderer::copyToLayer(const Rect& area) {
     const uint32_t width = area.getWidth();
     const uint32_t height = area.getHeight();
-    OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height);
+    OffscreenBuffer* buffer = mRenderState.layerPool().get(
+            mRenderState, width, height, mWideColorGamut);
     if (!area.isEmpty() && width != 0 && height != 0) {
         mCaches.textureState().activateTexture(0);
         mCaches.textureState().bindTexture(buffer->texture.id());
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index 4d76a3d..01ca367 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -54,12 +54,13 @@
         uint8_t spotShadowAlpha;
     };
 
-    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque,
+    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, bool wideColorGamut,
             const LightInfo& lightInfo)
             : mGlopReceiver(DefaultGlopReceiver)
             , mRenderState(renderState)
             , mCaches(caches)
             , mOpaque(opaque)
+            , mWideColorGamut(wideColorGamut)
             , mLightInfo(lightInfo) {
     }
 
@@ -118,6 +119,7 @@
     RenderState& mRenderState;
     Caches& mCaches;
     bool mOpaque;
+    bool mWideColorGamut;
     bool mHasDrawn = false;
 
     // render target state - setup by start/end layer/frame
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index aad81df..b587248 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -69,6 +69,7 @@
 bool Properties::forceDrawFrame = false;
 
 bool Properties::filterOutTestOverhead = false;
+bool Properties::disableVsync = false;
 
 static int property_get_int(const char* key, int defaultValue) {
     char buf[PROPERTY_VALUE_MAX] = {'\0',};
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 9db6449..91b4a2d 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -318,6 +318,12 @@
     // any overhead they add
     static bool filterOutTestOverhead;
 
+    // Workaround a device lockup in edge cases by switching to async mode
+    // instead of the default vsync (b/38372997). Only system_server should hit this.
+    // Any existing RenderProxy & Surface combination will be unaffected, only things
+    // created after changing this.
+    static bool disableVsync;
+
     // Used for testing only to change the render pipeline.
 #ifdef HWUI_GLES_WRAP_ENABLED
     static void overrideRenderPipelineType(RenderPipelineType);
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index 959059f..4ef31d5 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -120,6 +120,10 @@
 void Texture::upload(GLint internalFormat, uint32_t width, uint32_t height,
         GLenum format, GLenum type, const void* pixels) {
     GL_CHECKPOINT(MODERATE);
+
+    // We don't have color space information, we assume the data is gamma encoded
+    mIsLinear = false;
+
     bool needsAlloc = updateLayout(width, height, internalFormat, format, GL_TEXTURE_2D);
     if (!mId) {
         glGenTextures(1, &mId);
@@ -309,11 +313,16 @@
     bool rgba16fNeedsConversion = bitmap.colorType() == kRGBA_F16_SkColorType
             && internalFormat != GL_RGBA16F;
 
+    // RGBA16F is always linear extended sRGB
+    if (internalFormat == GL_RGBA16F) {
+        mIsLinear = true;
+    }
+
     mConnector.reset();
 
-    // RGBA16F is always extended sRGB, alpha masks don't have color profiles
+    // Alpha masks don't have color profiles
     // If an RGBA16F bitmap needs conversion, we know the target will be sRGB
-    if (internalFormat != GL_RGBA16F && internalFormat != GL_ALPHA && !rgba16fNeedsConversion) {
+    if (!mIsLinear && internalFormat != GL_ALPHA && !rgba16fNeedsConversion) {
         SkColorSpace* colorSpace = bitmap.info().colorSpace();
         // If the bitmap is sRGB we don't need conversion
         if (colorSpace != nullptr && !colorSpace->isSRGB()) {
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 55b74ed..7f742e6 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -88,7 +88,8 @@
      * The image data is undefined after calling this.
      */
     void resize(uint32_t width, uint32_t height, GLint internalFormat, GLint format) {
-        upload(internalFormat, width, height, format, GL_UNSIGNED_BYTE, nullptr);
+        upload(internalFormat, width, height, format,
+                internalFormat == GL_RGBA16F ? GL_HALF_FLOAT : GL_UNSIGNED_BYTE, nullptr);
     }
 
     /**
@@ -155,7 +156,7 @@
      * Returns true if this texture uses a linear encoding format.
      */
     constexpr bool isLinear() const {
-        return mInternalFormat == GL_RGBA16F;
+        return mIsLinear;
     }
 
     /**
@@ -219,6 +220,9 @@
     GLenum mMinFilter = GL_NEAREST_MIPMAP_LINEAR;
     GLenum mMagFilter = GL_LINEAR;
 
+    // Indicates whether the content of the texture is in linear space
+    bool mIsLinear = false;
+
     Caches& mCaches;
 
     std::unique_ptr<ColorSpaceConnector> mConnector;
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 095d32e..6d5ef1d 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -61,7 +61,7 @@
         const SkRect& dirty,
         const FrameBuilder::LightGeometry& lightGeometry,
         LayerUpdateQueue* layerUpdateQueue,
-        const Rect& contentDrawBounds, bool opaque,
+        const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
         const BakedOpRenderer::LightInfo& lightInfo,
         const std::vector<sp<RenderNode>>& renderNodes,
         FrameInfoVisualizer* profiler) {
@@ -85,7 +85,8 @@
             mRenderThread.getGrContext(), renderTargetDesc, &props));
 
     SkiaPipeline::updateLighting(lightGeometry, lightInfo);
-    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, wideColorGamut,
+            contentDrawBounds, surface);
     layerUpdateQueue->clear();
 
     // Draw visual debugging features
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index d5471de..aa29c8e 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -35,7 +35,7 @@
     bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
             const FrameBuilder::LightGeometry& lightGeometry,
             LayerUpdateQueue* layerUpdateQueue,
-            const Rect& contentDrawBounds, bool opaque,
+            const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo,
             const std::vector< sp<RenderNode> >& renderNodes,
             FrameInfoVisualizer* profiler) override;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index bbbbd5c..0bab793 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -72,16 +72,18 @@
 }
 
 void SkiaPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
-        LayerUpdateQueue* layerUpdateQueue, bool opaque,
+        LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
         const BakedOpRenderer::LightInfo& lightInfo) {
     updateLighting(lightGeometry, lightInfo);
     ATRACE_NAME("draw layers");
     renderVectorDrawableCache();
-    renderLayersImpl(*layerUpdateQueue, opaque);
+    renderLayersImpl(*layerUpdateQueue, opaque, wideColorGamut);
     layerUpdateQueue->clear();
 }
 
-void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
+void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers,
+        bool opaque, bool wideColorGamut) {
+    // TODO: Handle wide color gamut
     // Render all layers that need to be updated, in order.
     for (size_t i = 0; i < layers.entries().size(); i++) {
         RenderNode* layerNode = layers.entries()[i].renderNode.get();
@@ -129,12 +131,13 @@
 }
 
 bool SkiaPipeline::createOrUpdateLayer(RenderNode* node,
-        const DamageAccumulator& damageAccumulator) {
+        const DamageAccumulator& damageAccumulator, bool wideColorGamut) {
     SkSurface* layer = node->getLayerSurface();
     if (!layer || layer->width() != node->getWidth() || layer->height() != node->getHeight()) {
         SkImageInfo info = SkImageInfo::MakeN32Premul(node->getWidth(), node->getHeight());
         SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
         SkASSERT(mRenderThread.getGrContext() != nullptr);
+        // TODO: Handle wide color gamut requests
         node->setLayerSurface(
                 SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes,
                         info, 0, &props));
@@ -203,13 +206,13 @@
 }
 
 void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
-        const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds,
-        sk_sp<SkSurface> surface) {
+        const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut,
+        const Rect &contentDrawBounds, sk_sp<SkSurface> surface) {
 
     renderVectorDrawableCache();
 
     // draw all layers up front
-    renderLayersImpl(layers, opaque);
+    renderLayersImpl(layers, opaque, wideColorGamut);
 
     // initialize the canvas for the current frame
     SkCanvas* canvas = surface->getCanvas();
@@ -227,7 +230,7 @@
         }
     }
 
-    renderFrameImpl(layers, clip, nodes, opaque, contentDrawBounds, canvas);
+    renderFrameImpl(layers, clip, nodes, opaque, wideColorGamut, contentDrawBounds, canvas);
 
     if (skpCaptureEnabled() && recordingPicture) {
         sk_sp<SkPicture> picture = recorder->finishRecordingAsPicture();
@@ -260,8 +263,8 @@
 }
 
 void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip,
-        const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds,
-        SkCanvas* canvas) {
+        const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut,
+        const Rect &contentDrawBounds, SkCanvas* canvas) {
     SkAutoCanvasRestore saver(canvas, true);
     canvas->androidFramework_setDeviceClipRestriction(clip.roundOut());
 
@@ -388,7 +391,7 @@
     // each time a pixel would have been drawn.
     // Pass true for opaque so we skip the clear - the overdrawCanvas is already zero
     // initialized.
-    renderFrameImpl(layers, clip, nodes, true, contentDrawBounds, &overdrawCanvas);
+    renderFrameImpl(layers, clip, nodes, true, false, contentDrawBounds, &overdrawCanvas);
     sk_sp<SkImage> counts = offscreen->makeImageSnapshot();
 
     // Draw overdraw colors to the canvas.  The color filter will convert counts to colors.
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 6f5e719..19ffc46 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -39,15 +39,15 @@
     void unpinImages() override;
 
     void renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
-            LayerUpdateQueue* layerUpdateQueue, bool opaque,
+            LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo) override;
 
     bool createOrUpdateLayer(RenderNode* node,
-            const DamageAccumulator& damageAccumulator) override;
+            const DamageAccumulator& damageAccumulator, bool wideColorGamut) override;
 
     void renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
-            const std::vector< sp<RenderNode> >& nodes, bool opaque, const Rect &contentDrawBounds,
-            sk_sp<SkSurface> surface);
+            const std::vector< sp<RenderNode> >& nodes, bool opaque, bool wideColorGamut,
+            const Rect &contentDrawBounds, sk_sp<SkSurface> surface);
 
     std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; }
 
@@ -55,7 +55,7 @@
 
     static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
 
-    static void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque);
+    static void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, bool wideColorGamut);
 
     static bool skpCaptureEnabled() { return false; }
 
@@ -110,8 +110,8 @@
 
 private:
     void renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip,
-            const std::vector< sp<RenderNode> >& nodes, bool opaque, const Rect &contentDrawBounds,
-            SkCanvas* canvas);
+            const std::vector< sp<RenderNode> >& nodes, bool opaque, bool wideColorGamut,
+            const Rect &contentDrawBounds, SkCanvas* canvas);
 
     /**
      *  Debugging feature.  Draws a semi-transparent overlay on each pixel, indicating
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index f1ef9e6..e1ef71f7 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -66,7 +66,7 @@
         const SkRect& dirty,
         const FrameBuilder::LightGeometry& lightGeometry,
         LayerUpdateQueue* layerUpdateQueue,
-        const Rect& contentDrawBounds, bool opaque,
+        const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
         const BakedOpRenderer::LightInfo& lightInfo,
         const std::vector<sp<RenderNode>>& renderNodes,
         FrameInfoVisualizer* profiler) {
@@ -76,7 +76,8 @@
         return false;
     }
     SkiaPipeline::updateLighting(lightGeometry, lightInfo);
-    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer);
+    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, wideColorGamut,
+            contentDrawBounds, backBuffer);
     layerUpdateQueue->clear();
 
     // Draw visual debugging features
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 3481312..263206d 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -33,7 +33,7 @@
     bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
             const FrameBuilder::LightGeometry& lightGeometry,
             LayerUpdateQueue* layerUpdateQueue,
-            const Rect& contentDrawBounds, bool opaque,
+            const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo,
             const std::vector< sp<RenderNode> >& renderNodes,
             FrameInfoVisualizer* profiler) override;
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp
index a9bbb27..90b27c8 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.cpp
+++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp
@@ -35,17 +35,19 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
-        uint32_t viewportWidth, uint32_t viewportHeight)
+        uint32_t viewportWidth, uint32_t viewportHeight, bool wideColorGamut)
         : GpuMemoryTracker(GpuObjectType::OffscreenBuffer)
         , renderState(renderState)
         , viewportWidth(viewportWidth)
         , viewportHeight(viewportHeight)
-        , texture(caches) {
+        , texture(caches)
+        , wideColorGamut(wideColorGamut) {
     uint32_t width = computeIdealDimension(viewportWidth);
     uint32_t height = computeIdealDimension(viewportHeight);
     ATRACE_FORMAT("Allocate %ux%u HW Layer", width, height);
     caches.textureState().activateTexture(0);
-    texture.resize(width, height, caches.rgbaInternalFormat(), GL_RGBA);
+    texture.resize(width, height,
+            wideColorGamut ? GL_RGBA16F : caches.rgbaInternalFormat(), GL_RGBA);
     texture.blend = true;
     texture.setWrap(GL_CLAMP_TO_EDGE);
     // not setting filter on texture, since it's set when drawing, based on transform
@@ -127,7 +129,10 @@
     int deltaInt = int(lhs.width) - int(rhs.width);
     if (deltaInt != 0) return deltaInt;
 
-    return int(lhs.height) - int(rhs.height);
+    deltaInt = int(lhs.height) - int(rhs.height);
+    if (deltaInt != 0) return deltaInt;
+
+    return int(lhs.wideColorGamut) - int(rhs.wideColorGamut);
 }
 
 void OffscreenBufferPool::clear() {
@@ -139,10 +144,10 @@
 }
 
 OffscreenBuffer* OffscreenBufferPool::get(RenderState& renderState,
-        const uint32_t width, const uint32_t height) {
+        const uint32_t width, const uint32_t height, bool wideColorGamut) {
     OffscreenBuffer* layer = nullptr;
 
-    Entry entry(width, height);
+    Entry entry(width, height, wideColorGamut);
     auto iter = mPool.find(entry);
 
     if (iter != mPool.end()) {
@@ -154,7 +159,8 @@
         layer->viewportHeight = height;
         mSize -= layer->getSizeInBytes();
     } else {
-        layer = new OffscreenBuffer(renderState, Caches::getInstance(), width, height);
+        layer = new OffscreenBuffer(renderState, Caches::getInstance(),
+                width, height, wideColorGamut);
     }
 
     return layer;
@@ -174,7 +180,7 @@
         return layer;
     }
     putOrDelete(layer);
-    return get(renderState, width, height);
+    return get(renderState, width, height, layer->wideColorGamut);
 }
 
 void OffscreenBufferPool::dump() {
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h
index 26d4e36..d9422c9 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.h
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -43,7 +43,7 @@
 class OffscreenBuffer : GpuMemoryTracker {
 public:
     OffscreenBuffer(RenderState& renderState, Caches& caches,
-            uint32_t viewportWidth, uint32_t viewportHeight);
+            uint32_t viewportWidth, uint32_t viewportHeight, bool wideColorGamut = false);
     ~OffscreenBuffer();
 
     Rect getTextureCoordinates();
@@ -68,6 +68,8 @@
     uint32_t viewportHeight;
     Texture texture;
 
+    bool wideColorGamut = false;
+
     // Portion of layer that has been drawn to. Used to minimize drawing area when
     // drawing back to screen / parent FBO.
     Region region;
@@ -90,7 +92,7 @@
     ~OffscreenBufferPool();
 
     WARN_UNUSED_RESULT OffscreenBuffer* get(RenderState& renderState,
-            const uint32_t width, const uint32_t height);
+            const uint32_t width, const uint32_t height, bool wideColorGamut = false);
 
     WARN_UNUSED_RESULT OffscreenBuffer* resize(OffscreenBuffer* layer,
             const uint32_t width, const uint32_t height);
@@ -122,14 +124,16 @@
     struct Entry {
         Entry() {}
 
-        Entry(const uint32_t layerWidth, const uint32_t layerHeight)
+        Entry(const uint32_t layerWidth, const uint32_t layerHeight, bool wideColorGamut)
                 : width(OffscreenBuffer::computeIdealDimension(layerWidth))
-                , height(OffscreenBuffer::computeIdealDimension(layerHeight)) {}
+                , height(OffscreenBuffer::computeIdealDimension(layerHeight))
+                , wideColorGamut(wideColorGamut) {}
 
         explicit Entry(OffscreenBuffer* layer)
                 : layer(layer)
                 , width(layer->texture.width())
-                , height(layer->texture.height()) {
+                , height(layer->texture.height())
+                , wideColorGamut(layer->wideColorGamut) {
         }
 
         static int compare(const Entry& lhs, const Entry& rhs);
@@ -149,6 +153,7 @@
         OffscreenBuffer* layer = nullptr;
         uint32_t width = 0;
         uint32_t height = 0;
+        bool wideColorGamut = false;
     }; // struct Entry
 
     std::multiset<Entry> mPool;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index a79bf35..7799248 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -421,7 +421,7 @@
     SkRect windowDirty = computeDirtyRect(frame, &dirty);
 
     bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
-            mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes, &(profiler()));
+            mContentDrawBounds, mOpaque, mWideColorGamut, mLightInfo, mRenderNodes, &(profiler()));
 
     waitOnFences();
 
@@ -563,7 +563,8 @@
     // purposes when the frame is actually drawn
     node->setPropertyFieldsDirty(RenderNode::GENERIC);
 
-    mRenderPipeline->renderLayers(mLightGeometry, &mLayerUpdateQueue, mOpaque, mLightInfo);
+    mRenderPipeline->renderLayers(mLightGeometry, &mLayerUpdateQueue,
+            mOpaque, mWideColorGamut, mLightInfo);
 
     node->incStrong(nullptr);
     mPrefetchedLayers.insert(node);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 76623f9..b1f4050 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -76,7 +76,7 @@
      *  @return true if the layer has been created or updated
      */
     bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& dmgAccumulator) {
-        return mRenderPipeline->createOrUpdateLayer(node, dmgAccumulator);
+        return mRenderPipeline->createOrUpdateLayer(node, dmgAccumulator, mWideColorGamut);
     }
 
     /**
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index ecf686c..d6240e7 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -357,6 +357,9 @@
         }
     }
     mCurrentSurface = surface;
+    if (Properties::disableVsync) {
+        eglSwapInterval(mEglDisplay, 0);
+    }
     return true;
 }
 
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 46ac0d2..f9b6e384 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -59,7 +59,7 @@
     virtual bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
             const FrameBuilder::LightGeometry& lightGeometry,
             LayerUpdateQueue* layerUpdateQueue,
-            const Rect& contentDrawBounds, bool opaque,
+            const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo,
             const std::vector< sp<RenderNode> >& renderNodes,
             FrameInfoVisualizer* profiler) = 0;
@@ -73,11 +73,11 @@
     virtual bool isContextReady() = 0;
     virtual void onDestroyHardwareResources() = 0;
     virtual void renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
-            LayerUpdateQueue* layerUpdateQueue, bool opaque,
+            LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo) = 0;
     virtual TaskManager* getTaskManager() = 0;
     virtual bool createOrUpdateLayer(RenderNode* node,
-            const DamageAccumulator& damageAccumulator) = 0;
+            const DamageAccumulator& damageAccumulator, bool wideColorGamut) = 0;
     virtual bool pinImages(std::vector<SkImage*>& mutableImages) = 0;
     virtual bool pinImages(LsaVector<sk_sp<Bitmap>>& images) = 0;
     virtual void unpinImages() = 0;
diff --git a/libs/hwui/renderthread/OpenGLPipeline.cpp b/libs/hwui/renderthread/OpenGLPipeline.cpp
index e15d0eb..7283eb1 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.cpp
+++ b/libs/hwui/renderthread/OpenGLPipeline.cpp
@@ -58,7 +58,7 @@
 bool OpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
         const FrameBuilder::LightGeometry& lightGeometry,
         LayerUpdateQueue* layerUpdateQueue,
-        const Rect& contentDrawBounds, bool opaque,
+        const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
         const BakedOpRenderer::LightInfo& lightInfo,
         const std::vector< sp<RenderNode> >& renderNodes,
         FrameInfoVisualizer* profiler) {
@@ -77,7 +77,7 @@
     frameBuilder.deferRenderNodeScene(renderNodes, contentDrawBounds);
 
     BakedOpRenderer renderer(caches, mRenderThread.renderState(),
-            opaque, lightInfo);
+            opaque, wideColorGamut, lightInfo);
     frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
     ProfileRenderer profileRenderer(renderer);
     profiler->draw(profileRenderer);
@@ -184,14 +184,14 @@
 }
 
 void OpenGLPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
-        LayerUpdateQueue* layerUpdateQueue, bool opaque,
+        LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
         const BakedOpRenderer::LightInfo& lightInfo) {
     static const std::vector< sp<RenderNode> > emptyNodeList;
     auto& caches = Caches::getInstance();
     FrameBuilder frameBuilder(*layerUpdateQueue, lightGeometry, caches);
     layerUpdateQueue->clear();
-    BakedOpRenderer renderer(caches, mRenderThread.renderState(),
-            opaque, lightInfo);
+    // TODO: Handle wide color gamut contexts
+    BakedOpRenderer renderer(caches, mRenderThread.renderState(), opaque, wideColorGamut, lightInfo);
     LOG_ALWAYS_FATAL_IF(renderer.didDraw(), "shouldn't draw in buildlayer case");
     frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
 }
@@ -205,12 +205,13 @@
 }
 
 bool OpenGLPipeline::createOrUpdateLayer(RenderNode* node,
-        const DamageAccumulator& damageAccumulator) {
+        const DamageAccumulator& damageAccumulator, bool wideColorGamut) {
     RenderState& renderState = mRenderThread.renderState();
     OffscreenBufferPool& layerPool = renderState.layerPool();
     bool transformUpdateNeeded = false;
     if (node->getLayer() == nullptr) {
-        node->setLayer(layerPool.get(renderState, node->getWidth(), node->getHeight()));
+        node->setLayer(layerPool.get(renderState,
+                node->getWidth(), node->getHeight(), wideColorGamut));
         transformUpdateNeeded = true;
     } else if (!layerMatchesWH(node->getLayer(), node->getWidth(), node->getHeight())) {
         // TODO: remove now irrelevant, currently enqueued damage (respecting damage ordering)
diff --git a/libs/hwui/renderthread/OpenGLPipeline.h b/libs/hwui/renderthread/OpenGLPipeline.h
index 0e8c3f5..4ca19fb 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.h
+++ b/libs/hwui/renderthread/OpenGLPipeline.h
@@ -36,7 +36,7 @@
     bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
             const FrameBuilder::LightGeometry& lightGeometry,
             LayerUpdateQueue* layerUpdateQueue,
-            const Rect& contentDrawBounds, bool opaque,
+            const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo,
             const std::vector< sp<RenderNode> >& renderNodes,
             FrameInfoVisualizer* profiler) override;
@@ -50,11 +50,11 @@
     bool isContextReady() override;
     void onDestroyHardwareResources() override;
     void renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
-            LayerUpdateQueue* layerUpdateQueue, bool opaque,
+            LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo) override;
     TaskManager* getTaskManager() override;
     bool createOrUpdateLayer(RenderNode* node,
-            const DamageAccumulator& damageAccumulator) override;
+            const DamageAccumulator& damageAccumulator, bool wideColorGamut) override;
     bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
     bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override;
     void unpinImages() override;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index ec56c31..80c2955 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -18,6 +18,7 @@
 
 #include "DeferredLayerUpdater.h"
 #include "DisplayList.h"
+#include "Properties.h"
 #include "Readback.h"
 #include "Rect.h"
 #include "renderthread/CanvasContext.h"
@@ -709,6 +710,10 @@
     thread.queue(task);
 }
 
+void RenderProxy::disableVsync() {
+    Properties::disableVsync = true;
+}
+
 void RenderProxy::post(RenderTask* task) {
     mRenderThread.queue(task);
 }
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index e1e2808..31f0ce6 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -138,6 +138,8 @@
     static int copyGraphicBufferInto(GraphicBuffer* buffer, SkBitmap* bitmap);
 
     static void onBitmapDestroyed(uint32_t pixelRefId);
+
+    ANDROID_API static void disableVsync();
 private:
     RenderThread& mRenderThread;
     CanvasContext* mContext;
diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
index 398e7a8..a5e85df 100644
--- a/libs/hwui/tests/microbench/FrameBuilderBench.cpp
+++ b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
@@ -83,7 +83,7 @@
                     sLightGeometry, caches);
             frameBuilder.deferRenderNode(*node);
 
-            BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
+            BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo);
             frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
             benchmark::DoNotOptimize(&renderer);
         }
@@ -142,7 +142,7 @@
                     sLightGeometry, Caches::getInstance());
             frameBuilder.deferRenderNode(*node);
 
-            BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
+            BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo);
             frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
             benchmark::DoNotOptimize(&renderer);
         }
diff --git a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
index c46592c..b0ef11f 100644
--- a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
@@ -37,7 +37,7 @@
 class ValidatingBakedOpRenderer : public BakedOpRenderer {
 public:
     ValidatingBakedOpRenderer(RenderState& renderState, std::function<void(const Glop& glop)> validator)
-            : BakedOpRenderer(Caches::getInstance(), renderState, true, sLightInfo)
+            : BakedOpRenderer(Caches::getInstance(), renderState, true, false, sLightInfo)
             , mValidator(validator) {
         mGlopReceiver = ValidatingGlopReceiver;
     }
diff --git a/libs/hwui/tests/unit/BakedOpRendererTests.cpp b/libs/hwui/tests/unit/BakedOpRendererTests.cpp
index 380062a..603599c 100644
--- a/libs/hwui/tests/unit/BakedOpRendererTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpRendererTests.cpp
@@ -24,7 +24,8 @@
 const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 };
 
 RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, startRepaintLayer_clear) {
-    BakedOpRenderer renderer(Caches::getInstance(), renderThread.renderState(), true, sLightInfo);
+    BakedOpRenderer renderer(Caches::getInstance(), renderThread.renderState(),
+            true, false, sLightInfo);
     OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200u, 200u);
 
     layer.dirty(Rect(200, 200));
diff --git a/libs/hwui/tests/unit/LeakCheckTests.cpp b/libs/hwui/tests/unit/LeakCheckTests.cpp
index 6c42ca1..19d7ef5 100644
--- a/libs/hwui/tests/unit/LeakCheckTests.cpp
+++ b/libs/hwui/tests/unit/LeakCheckTests.cpp
@@ -45,7 +45,7 @@
     FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
             sLightGeometery, Caches::getInstance());
     frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-    BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
+    BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo);
     frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
 }
 
@@ -62,6 +62,6 @@
     FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
             sLightGeometery, Caches::getInstance());
     frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-    BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
+    BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo);
     frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
 }
diff --git a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
index 6cd595a..919852f 100644
--- a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
+++ b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
@@ -41,6 +41,19 @@
     EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes());
 }
 
+RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, constructWideColorGamut) {
+    OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 49u, 149u, true);
+    EXPECT_EQ(49u, layer.viewportWidth);
+    EXPECT_EQ(149u, layer.viewportHeight);
+
+    EXPECT_EQ(64u, layer.texture.width());
+    EXPECT_EQ(192u, layer.texture.height());
+
+    EXPECT_TRUE(layer.wideColorGamut);
+
+    EXPECT_EQ(64u * 192u * 8u, layer.getSizeInBytes());
+}
+
 RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, getTextureCoordinates) {
     OffscreenBuffer layerAligned(renderThread.renderState(), Caches::getInstance(), 256u, 256u);
     EXPECT_EQ(Rect(0, 1, 1, 0),
@@ -88,6 +101,47 @@
     EXPECT_EQ(0u, pool.getCount());
 }
 
+RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, getPutClearWideColorGamut) {
+    OffscreenBufferPool pool;
+
+    auto layer = pool.get(renderThread.renderState(), 100u, 200u, true);
+    EXPECT_EQ(100u, layer->viewportWidth);
+    EXPECT_EQ(200u, layer->viewportHeight);
+    EXPECT_TRUE(layer->wideColorGamut);
+
+    ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize());
+
+    pool.putOrDelete(layer);
+    ASSERT_EQ(layer->getSizeInBytes(), pool.getSize());
+
+    auto layer2 = pool.get(renderThread.renderState(), 102u, 202u, true);
+    EXPECT_EQ(layer, layer2) << "layer should be recycled";
+    ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer";
+
+    pool.putOrDelete(layer2);
+    EXPECT_EQ(1u, pool.getCount());
+    pool.clear();
+    EXPECT_EQ(0u, pool.getSize());
+    EXPECT_EQ(0u, pool.getCount());
+
+    // add non wide gamut layer
+    auto layer3 = pool.get(renderThread.renderState(), 100u, 200u);
+    EXPECT_FALSE(layer3->wideColorGamut);
+    pool.putOrDelete(layer3);
+    EXPECT_EQ(1u, pool.getCount());
+
+    auto layer4 = pool.get(renderThread.renderState(), 100u, 200u, true);
+    EXPECT_TRUE(layer4->wideColorGamut);
+    EXPECT_EQ(1u, pool.getCount());
+    ASSERT_NE(layer3, layer4);
+
+    pool.putOrDelete(layer4);
+
+    pool.clear();
+    EXPECT_EQ(0u, pool.getSize());
+    EXPECT_EQ(0u, pool.getCount());
+}
+
 RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, resize) {
     OffscreenBufferPool pool;
 
@@ -123,6 +177,43 @@
     pool.putOrDelete(layer2);
 }
 
+RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, resizeWideColorGamut) {
+    OffscreenBufferPool pool;
+
+    auto layer = pool.get(renderThread.renderState(), 64u, 64u, true);
+
+    // resize in place
+    ASSERT_EQ(layer, pool.resize(layer, 60u, 55u));
+    EXPECT_EQ(60u, layer->viewportWidth);
+    EXPECT_EQ(55u, layer->viewportHeight);
+    EXPECT_EQ(64u, layer->texture.width());
+    EXPECT_EQ(64u, layer->texture.height());
+
+    EXPECT_TRUE(layer->wideColorGamut);
+    EXPECT_EQ(64u * 64u * 8u, layer->getSizeInBytes());
+
+    // resized to use different object in pool
+    auto layer2 = pool.get(renderThread.renderState(), 128u, 128u, true);
+    pool.putOrDelete(layer2);
+    ASSERT_EQ(1u, pool.getCount());
+
+    // add a non-wide gamut layer
+    auto layer3 = pool.get(renderThread.renderState(), 128u, 128u);
+    pool.putOrDelete(layer3);
+    ASSERT_EQ(2u, pool.getCount());
+
+    ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u));
+    EXPECT_EQ(120u, layer2->viewportWidth);
+    EXPECT_EQ(125u, layer2->viewportHeight);
+    EXPECT_EQ(128u, layer2->texture.width());
+    EXPECT_EQ(128u, layer2->texture.height());
+
+    EXPECT_TRUE(layer2->wideColorGamut);
+    EXPECT_EQ(128u * 128u * 8u, layer2->getSizeInBytes());
+
+    pool.putOrDelete(layer2);
+}
+
 RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, putAndDestroy) {
     OffscreenBufferPool pool;
     // layer too big to return to the pool
@@ -153,3 +244,4 @@
 
     EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer));
 }
+
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 686d06f..4c3e182 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -450,7 +450,7 @@
     LayerUpdateQueue layerUpdateQueue;
     layerUpdateQueue.enqueueLayerWithDamage(child.get(),
             android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT));
-    SkiaPipeline::renderLayersImpl(layerUpdateQueue, true);
+    SkiaPipeline::renderLayersImpl(layerUpdateQueue, true, false);
     EXPECT_EQ(1, drawCounter);  //assert index 0 is drawn on the layer
 
     RenderNodeDrawable drawable(parent.get(), surfaceLayer1->getCanvas(), true);
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index a895cba..b397b15 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -51,7 +51,8 @@
     auto surface = SkSurface::MakeRasterN32Premul(1, 1);
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes,
+            opaque, false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
 }
 
@@ -72,10 +73,12 @@
     auto surface = SkSurface::MakeRasterN32Premul(2, 2);
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes,
+            true, false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes,
+            false, false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned int)SK_ColorTRANSPARENT);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
 }
@@ -94,7 +97,8 @@
     auto surface = SkSurface::MakeRasterN32Premul(2, 2);
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes,
+            true, false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
     ASSERT_EQ(TestUtils::getColor(surface, 1, 0), SK_ColorBLUE);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorRED);
@@ -135,7 +139,7 @@
     lightGeometry.center = { 0.0f, 0.0f, 0.0f };
     BakedOpRenderer::LightInfo lightInfo;
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
-    pipeline->renderLayers(lightGeometry, &layerUpdateQueue, opaque, lightInfo);
+    pipeline->renderLayers(lightGeometry, &layerUpdateQueue, opaque, false, lightInfo);
     ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorRED);
     ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorBLUE);
     ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 1), SK_ColorWHITE);
@@ -166,32 +170,38 @@
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
 
     // Single draw, should be white.
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque,
+            false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
 
     // 1 Overdraw, should be blue blended onto white.
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque,
+            false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffd0d0ff);
 
     // 2 Overdraw, should be green blended onto white
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque,
+            false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffd0ffd0);
 
     // 3 Overdraw, should be pink blended onto white.
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque,
+            false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffffc0c0);
 
     // 4 Overdraw, should be red blended onto white.
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque,
+            false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffff8080);
 
     // 5 Overdraw, should be red blended onto white.
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque,
+            false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffff8080);
 }
 
@@ -278,7 +288,7 @@
     SkRect dirty = SkRect::MakeWH(800, 600);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
     sk_sp<DeferLayer<DeferTestCanvas>> surface(new DeferLayer<DeferTestCanvas>());
-    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, false, contentDrawBounds, surface);
     EXPECT_EQ(4, surface->canvas()->mDrawCounter);
 }
 
@@ -308,7 +318,7 @@
     SkRect dirty = SkRect::MakeLTRB(10, 20, 30, 40);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
     sk_sp<DeferLayer<ClippedTestCanvas>> surface(new DeferLayer<ClippedTestCanvas>());
-    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true,
+    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, false,
             SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface);
     EXPECT_EQ(1, surface->canvas()->mDrawCounter);
 }
@@ -339,7 +349,7 @@
     SkRect dirty = SkRect::MakeLTRB(10, 10, 40, 40);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
     sk_sp<DeferLayer<ClipReplaceTestCanvas>> surface(new DeferLayer<ClipReplaceTestCanvas>());
-    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true,
+    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, false,
             SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface);
     EXPECT_EQ(1, surface->canvas()->mDrawCounter);
 }
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 47ecf32..6ef3091 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -758,6 +758,8 @@
 
     public static native int systemReady();
 
+    public static native float getStreamVolumeDB(int stream, int index, int device);
+
     // Items shared with audio service
 
     /**
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 7f8140a..92ffae0 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -15,7 +15,9 @@
  */
 
 package android.media.soundtrigger;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
 
+import android.app.PendingIntent;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -23,6 +25,10 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.SoundModel;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.os.Handler;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
@@ -178,4 +184,144 @@
             return mGenericSoundModel;
         }
     }
+
+
+    /**
+     * Default message type.
+     * @hide
+     */
+    public static final int FLAG_MESSAGE_TYPE_UNKNOWN = -1;
+    /**
+     * Contents of EXTRA_MESSAGE_TYPE extra for a RecognitionEvent.
+     * @hide
+     */
+    public static final int FLAG_MESSAGE_TYPE_RECOGNITION_EVENT = 0;
+    /**
+     * Contents of EXTRA_MESSAGE_TYPE extra for recognition error events.
+     * @hide
+     */
+    public static final int FLAG_MESSAGE_TYPE_RECOGNITION_ERROR = 1;
+    /**
+     * Contents of EXTRA_MESSAGE_TYPE extra for a recognition paused events.
+     * @hide
+     */
+    public static final int FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED = 2;
+    /**
+     * Contents of EXTRA_MESSAGE_TYPE extra for recognition resumed events.
+     * @hide
+     */
+    public static final int FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED = 3;
+
+    /**
+     * Extra key in the intent for the type of the message.
+     * @hide
+     */
+    public static final String EXTRA_MESSAGE_TYPE = "android.media.soundtrigger.MESSAGE_TYPE";
+    /**
+     * Extra key in the intent that holds the RecognitionEvent parcelable.
+     * @hide
+     */
+    public static final String EXTRA_RECOGNITION_EVENT = "android.media.soundtrigger.RECOGNITION_EVENT";
+    /**
+     * Extra key in the intent that holds the status in an error message.
+     * @hide
+     */
+    public static final String EXTRA_STATUS = "android.media.soundtrigger.STATUS";
+
+    /**
+     * Loads a given sound model into the sound trigger. Note the model will be unloaded if there is
+     * an error/the system service is restarted.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public int loadSoundModel(SoundModel soundModel) {
+        if (soundModel == null) {
+            return STATUS_ERROR;
+        }
+
+        try {
+            switch (soundModel.type) {
+                case SoundModel.TYPE_GENERIC_SOUND:
+                    return mSoundTriggerService.loadGenericSoundModel(
+                            (GenericSoundModel) soundModel);
+                case SoundModel.TYPE_KEYPHRASE:
+                    return mSoundTriggerService.loadKeyphraseSoundModel(
+                            (KeyphraseSoundModel) soundModel);
+                default:
+                    Slog.e(TAG, "Unkown model type");
+                    return STATUS_ERROR;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Starts recognition on the given model id. All events from the model will be sent to the
+     * PendingIntent.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public int startRecognition(UUID soundModelId, PendingIntent callbackIntent,
+            RecognitionConfig config) {
+        if (soundModelId == null || callbackIntent == null || config == null) {
+            return STATUS_ERROR;
+        }
+        try {
+            return mSoundTriggerService.startRecognitionForIntent(new ParcelUuid(soundModelId),
+                    callbackIntent, config);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Stops the given model's recognition.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public int stopRecognition(UUID soundModelId) {
+        if (soundModelId == null) {
+            return STATUS_ERROR;
+        }
+        try {
+            return mSoundTriggerService.stopRecognitionForIntent(new ParcelUuid(soundModelId));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes the given model from memory. Will also stop any pending recognitions.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public int unloadSoundModel(UUID soundModelId) {
+        if (soundModelId == null) {
+            return STATUS_ERROR;
+        }
+        try {
+            return mSoundTriggerService.unloadSoundModel(
+                    new ParcelUuid(soundModelId));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns true if the given model has had detection started on it.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public boolean isRecognitionActive(UUID soundModelId) {
+        if (soundModelId == null) {
+            return false;
+        }
+        try {
+            return mSoundTriggerService.isRecognitionActive(
+                    new ParcelUuid(soundModelId));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 52fadfaa..ff115f70 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -176,6 +176,7 @@
 }
 
 void JNIImageReaderContext::returnBufferItem(BufferItem* buffer) {
+    buffer->mGraphicBuffer = nullptr;
     mBuffers.push_back(buffer);
 }
 
diff --git a/packages/Osu2/Android.mk b/packages/Osu2/Android.mk
new file mode 100644
index 0000000..05586f0
--- /dev/null
+++ b/packages/Osu2/Android.mk
@@ -0,0 +1,18 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := Osu2
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+
+include $(BUILD_PACKAGE)
+
+########################
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/Osu2/AndroidManifest.xml b/packages/Osu2/AndroidManifest.xml
new file mode 100644
index 0000000..236b120b
--- /dev/null
+++ b/packages/Osu2/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.osu">
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application
+    android:enabled="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name">
+        <activity android:name=".MainActivity" android:exported="true">
+        </activity>
+    </application>
+
+</manifest>
diff --git a/packages/Osu2/res/layout/activity_main.xml b/packages/Osu2/res/layout/activity_main.xml
new file mode 100644
index 0000000..f9504c9
--- /dev/null
+++ b/packages/Osu2/res/layout/activity_main.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+</LinearLayout>
diff --git a/packages/Osu2/res/mipmap-hdpi/ic_launcher.png b/packages/Osu2/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/packages/Osu2/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/packages/Osu2/res/mipmap-mdpi/ic_launcher.png b/packages/Osu2/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/packages/Osu2/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/packages/Osu2/res/mipmap-xhdpi/ic_launcher.png b/packages/Osu2/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/packages/Osu2/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/Osu2/res/mipmap-xxhdpi/ic_launcher.png b/packages/Osu2/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/packages/Osu2/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/Osu2/res/mipmap-xxxhdpi/ic_launcher.png b/packages/Osu2/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/packages/Osu2/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/Osu2/res/values-w820dp/dimens.xml b/packages/Osu2/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/packages/Osu2/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/packages/Osu2/res/values/colors.xml b/packages/Osu2/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/packages/Osu2/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/packages/Osu2/res/values/dimens.xml b/packages/Osu2/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/packages/Osu2/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/packages/Osu2/res/values/strings.xml b/packages/Osu2/res/values/strings.xml
new file mode 100644
index 0000000..e5b1af6
--- /dev/null
+++ b/packages/Osu2/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">Passpoint Online Sign-Up</string>
+</resources>
diff --git a/core/java/android/service/euicc/EraseResult.aidl b/packages/Osu2/src/com/android/osu/Constants.java
similarity index 66%
rename from core/java/android/service/euicc/EraseResult.aidl
rename to packages/Osu2/src/com/android/osu/Constants.java
index e28a097..cd046d8 100644
--- a/core/java/android/service/euicc/EraseResult.aidl
+++ b/packages/Osu2/src/com/android/osu/Constants.java
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package android.service.euicc;
+package com.android.osu;
 
-parcelable EraseResult;
+public final class Constants {
+    public static final String INTENT_EXTRA_COMMAND = "com.android.osu.extra.COMMAND";
+    public static final String INTENT_EXTRA_OSU_PROVIDER = "com.android.osu.extra.OSU_PROVIDER";
+
+    public static final String COMMAND_PROVISION = "Provision";
+}
\ No newline at end of file
diff --git a/packages/Osu2/src/com/android/osu/MainActivity.java b/packages/Osu2/src/com/android/osu/MainActivity.java
new file mode 100644
index 0000000..4e2136b
--- /dev/null
+++ b/packages/Osu2/src/com/android/osu/MainActivity.java
@@ -0,0 +1,76 @@
+/*
+ * 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.osu;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.wifi.hotspot2.OsuProvider;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Main entry point for the OSU (Online Sign-Up) app.
+ */
+public class MainActivity extends Activity {
+    private static final String TAG = "OSU_MainActivity";
+    private OsuService mService;
+
+    @Override
+    protected void onCreate(Bundle saveInstanceState) {
+        super.onCreate(saveInstanceState);
+
+        Intent intent = getIntent();
+        if (intent == null) {
+            Log.e(TAG, "Intent not provided");
+            finish();
+        }
+
+        if (!intent.hasExtra(Constants.INTENT_EXTRA_COMMAND)) {
+            Log.e(TAG, "Command not provided");
+            finish();
+        }
+
+        String command = intent.getStringExtra(Constants.INTENT_EXTRA_COMMAND);
+        switch (command) {
+            case Constants.COMMAND_PROVISION:
+                if (!startProvisionService(intent.getParcelableExtra(
+                        Constants.INTENT_EXTRA_OSU_PROVIDER))) {
+                    finish();
+                }
+                break;
+            default:
+                Log.e(TAG, "Unknown command: '" + command + "'");
+                finish();
+                break;
+        }
+    }
+
+    /**
+     * Start the {@link ProvisionService} to perform provisioning tasks.
+     *
+     * @return true if service is started
+     */
+    private boolean startProvisionService(OsuProvider provider) {
+        if (provider == null) {
+            Log.e(TAG, "OSU Provider not provided");
+            return false;
+        }
+        mService = new ProvisionService(this, provider);
+        mService.start();
+        return true;
+    }
+}
diff --git a/packages/Osu2/src/com/android/osu/NetworkConnection.java b/packages/Osu2/src/com/android/osu/NetworkConnection.java
new file mode 100644
index 0000000..9f5b929
--- /dev/null
+++ b/packages/Osu2/src/com/android/osu/NetworkConnection.java
@@ -0,0 +1,206 @@
+/*
+ * 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.osu;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Network;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Responsible for setup/monitor on a Wi-Fi connection.
+ */
+public class NetworkConnection {
+    private static final String TAG = "OSU_NetworkConnection";
+
+    private final WifiManager mWifiManager;
+    private final Callbacks mCallbacks;
+    private final int mNetworkId;
+    private boolean mConnected = false;
+
+    /**
+     * Callbacks on Wi-Fi connection state changes.
+     */
+    public interface Callbacks {
+        /**
+         * Invoked when network connection is established with IP connectivity.
+         *
+         * @param network {@link Network} associated with the connected network.
+         */
+        public void onConnected(Network network);
+
+        /**
+         * Invoked when the targeted network is disconnected.
+         */
+        public void onDisconnected();
+
+        /**
+         * Invoked when network connection is not established within the pre-defined timeout.
+         */
+        public void onTimeout();
+    }
+
+    /**
+     * Create an instance of {@link NetworkConnection} for the specified Wi-Fi network.
+     * The Wi-Fi network (specified by its SSID) will be added/enabled as part of this object
+     * creation.
+     *
+     * {@link #teardown} will need to be invoked once you're done with this connection,
+     * to remove the given Wi-Fi network from the framework.
+     *
+     * @param context The application context
+     * @param handler The handler to dispatch the processing of received broadcast intents
+     * @param ssid The SSID to connect to
+     * @param nai The network access identifier associated with the AP
+     * @param callbacks The callbacks to be invoked on network change events
+     * @throws IOException when failed to add/enable the specified Wi-Fi network
+     */
+    public NetworkConnection(Context context, Handler handler, WifiSsid ssid, String nai,
+            Callbacks callbacks) throws IOException {
+        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        mCallbacks = callbacks;
+        mNetworkId = connect(ssid, nai);
+
+        // TODO(zqiu): setup alarm to timed out the connection attempt.
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                    handleNetworkStateChanged(
+                            intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO),
+                            intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO));
+                }
+            }
+        };
+        // Provide a Handler so that the onReceive call will be run on the specified handler
+        // thread instead of the main thread.
+        context.registerReceiver(receiver, filter, null, handler);
+    }
+
+    /**
+     * Teardown the network connection by removing the network.
+     */
+    public void teardown() {
+        mWifiManager.removeNetwork(mNetworkId);
+    }
+
+    /**
+     * Connect to a OSU Wi-Fi network specified by the given SSID. The security type of the Wi-Fi
+     * network is either open or OSEN (OSU Server-only authenticated layer 2 Encryption Network).
+     * When network access identifier is provided, OSEN is used.
+     *
+     * @param ssid The SSID to connect to
+     * @param nai Network access identifier of the network
+     *
+     * @return unique ID associated with the network
+     * @throws IOException
+     */
+    private int connect(WifiSsid ssid, String nai) throws IOException {
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "\"" + ssid.toString() + "\"";
+        if (TextUtils.isEmpty(nai)) {
+            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        } else {
+            // TODO(zqiu): configuration setup for OSEN.
+        }
+        int networkId = mWifiManager.addNetwork(config);
+        if (networkId < 0) {
+            throw new IOException("Failed to add OSU network");
+        }
+        if (!mWifiManager.enableNetwork(networkId, true)) {
+            throw new IOException("Failed to enable OSU network");
+        }
+        return networkId;
+    }
+
+    /**
+     * Handle network state changed events.
+     *
+     * @param networkInfo {@link NetworkInfo} indicating the current network state
+     * @param wifiInfo {@link WifiInfo} associated with the current network when connected
+     */
+    private void handleNetworkStateChanged(NetworkInfo networkInfo, WifiInfo wifiInfo) {
+        if (networkInfo == null) {
+            Log.e(TAG, "NetworkInfo not provided for network state changed event");
+            return;
+        }
+        switch (networkInfo.getDetailedState()) {
+            case CONNECTED:
+                handleConnectedEvent(wifiInfo);
+                break;
+            case DISCONNECTED:
+                handleDisconnectedEvent();
+                break;
+            default:
+                Log.d(TAG, "Ignore uninterested state: " + networkInfo.getDetailedState());
+                break;
+        }
+    }
+
+    /**
+     * Handle network connected event.
+     *
+     * @param wifiInfo {@link WifiInfo} associated with the current connection
+     */
+    private void handleConnectedEvent(WifiInfo wifiInfo) {
+        if (mConnected) {
+            // No-op if already connected.
+            return;
+        }
+        if (wifiInfo == null) {
+            Log.e(TAG, "WifiInfo not provided for connected event");
+            return;
+        }
+        if (wifiInfo.getNetworkId() != mNetworkId) {
+            return;
+        }
+        Network network = mWifiManager.getCurrentNetwork();
+        if (network == null) {
+            Log.e(TAG, "Current network is not set");
+            return;
+        }
+        mConnected = true;
+        mCallbacks.onConnected(network);
+    }
+
+    /**
+     * Handle network disconnected event.
+     */
+    private void handleDisconnectedEvent() {
+        if (!mConnected) {
+            // No-op if not connected, most likely a disconnect event for a different network.
+            return;
+        }
+        mConnected = false;
+        mCallbacks.onDisconnected();
+    }
+}
diff --git a/core/java/android/service/euicc/EraseResult.aidl b/packages/Osu2/src/com/android/osu/OsuService.java
similarity index 65%
copy from core/java/android/service/euicc/EraseResult.aidl
copy to packages/Osu2/src/com/android/osu/OsuService.java
index e28a097..46a3c84 100644
--- a/core/java/android/service/euicc/EraseResult.aidl
+++ b/packages/Osu2/src/com/android/osu/OsuService.java
@@ -14,6 +14,20 @@
  * limitations under the License.
  */
 
-package android.service.euicc;
+package com.android.osu;
 
-parcelable EraseResult;
+/**
+ * Abstraction for services that can be performed by the OSU app, such as provisioning,
+ * subscription remediation, and etc.
+ */
+public interface OsuService {
+    /**
+     * Start the service.
+     */
+    public void start();
+
+    /**
+     * Stop the service.
+     */
+    public void stop();
+}
diff --git a/packages/Osu2/src/com/android/osu/ProvisionService.java b/packages/Osu2/src/com/android/osu/ProvisionService.java
new file mode 100644
index 0000000..b1d43b2
--- /dev/null
+++ b/packages/Osu2/src/com/android/osu/ProvisionService.java
@@ -0,0 +1,119 @@
+/*
+ * 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.osu;
+
+import android.content.Context;
+import android.net.Network;
+import android.net.wifi.hotspot2.OsuProvider;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Service responsible for performing Passpoint subscription provisioning tasks.
+ * This service will run on a separate thread to avoid blocking on the Main thread.
+ */
+public class ProvisionService implements OsuService {
+    private static final String TAG = "OSU_ProvisionService";
+    private static final int COMMAND_START = 1;
+    private static final int COMMAND_STOP = 2;
+
+    private final Context mContext;
+    private final HandlerThread mHandlerThread;
+    private final ServiceHandler mServiceHandler;
+    private final OsuProvider mProvider;
+
+    private boolean mStarted = false;
+    private NetworkConnection mNetworkConnection = null;
+
+    public ProvisionService(Context context, OsuProvider provider) {
+        mContext = context;
+        mProvider = provider;
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mServiceHandler = new ServiceHandler(mHandlerThread.getLooper());
+    }
+
+    @Override
+    public void start() {
+        mServiceHandler.sendMessage(mServiceHandler.obtainMessage(COMMAND_START));
+    }
+
+    @Override
+    public void stop() {
+        mServiceHandler.sendMessage(mServiceHandler.obtainMessage(COMMAND_STOP));
+    }
+
+    /**
+     * Handler class for handling commands to the ProvisionService.
+     */
+    private final class ServiceHandler extends Handler {
+        public ServiceHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case COMMAND_START:
+                    if (mStarted) {
+                        Log.e(TAG, "Service already started");
+                        return;
+                    }
+                    try {
+                        // Initiate network connection to the OSU AP.
+                        mNetworkConnection = new NetworkConnection(
+                                mContext, this, mProvider.getOsuSsid(),
+                                mProvider.getNetworkAccessIdentifier(), new NetworkCallbacks());
+                        mStarted = true;
+                    } catch (IOException e) {
+                        // TODO(zqiu): broadcast failure event via LocalBroadcastManager.
+                    }
+                    break;
+                case COMMAND_STOP:
+                    if (!mStarted) {
+                        Log.e(TAG, "Service not started");
+                        return;
+                    }
+                    Log.e(TAG, "Stop provision service");
+                    break;
+                default:
+                    Log.e(TAG, "Unknown command: " + msg.what);
+                    break;
+            }
+        }
+    }
+
+    private final class NetworkCallbacks implements NetworkConnection.Callbacks {
+        @Override
+        public void onConnected(Network network) {
+            Log.d(TAG, "Connected to OSU AP");
+        }
+
+        @Override
+        public void onDisconnected() {
+        }
+
+        @Override
+        public void onTimeout() {
+        }
+    }
+}
diff --git a/packages/Osu2/tests/Android.mk b/packages/Osu2/tests/Android.mk
new file mode 100644
index 0000000..4b6e0e6
--- /dev/null
+++ b/packages/Osu2/tests/Android.mk
@@ -0,0 +1,43 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE := platform
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_JACK_FLAGS := --multi-dex native
+
+LOCAL_PACKAGE_NAME := OsuTests
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_INSTRUMENTATION_FOR := Osu2
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    mockito-target-minus-junit4 \
+    frameworks-base-testutils
+
+# Code coverage puts us over the dex limit, so enable multi-dex for coverage-enabled builds
+ifeq (true,$(EMMA_INSTRUMENT))
+LOCAL_JACK_FLAGS := --multi-dex native
+LOCAL_DX_FLAGS := --multi-dex
+endif # EMMA_INSTRUMENT
+
+include $(BUILD_PACKAGE)
diff --git a/packages/Osu2/tests/AndroidManifest.xml b/packages/Osu2/tests/AndroidManifest.xml
new file mode 100644
index 0000000..e22c112
--- /dev/null
+++ b/packages/Osu2/tests/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.osu.tests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:label="OsuTestDummyLabel"
+                  android:name="OsuTestDummyName">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.osu"
+        android:label="OSU App Tests">
+    </instrumentation>
+
+</manifest>
diff --git a/packages/Osu2/tests/README.md b/packages/Osu2/tests/README.md
new file mode 100644
index 0000000..dbfa79c
--- /dev/null
+++ b/packages/Osu2/tests/README.md
@@ -0,0 +1,45 @@
+# OSU Unit Tests
+This package contains unit tests for the OSU app based on the
+[Android Testing Support Library](http://developer.android.com/tools/testing-support-library/index.html).
+The test cases are built using the [JUnit](http://junit.org/) and [Mockito](http://mockito.org/)
+libraries.
+
+## Running Tests
+The easiest way to run tests is simply run
+
+```
+frameworks/base/packages/Osu2/tests/runtests.sh
+```
+
+`runtests.sh` will build the test project and all of its dependencies and push the APK to the
+connected device. It will then run the tests on the device.
+
+To enable syncing data to the device for first time after clean reflash:
+1. adb disable-verity
+2. adb reboot
+3. adb remount
+
+See below for a few example of options to limit which tests are run.
+See the
+[AndroidJUnitRunner Documentation](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html)
+for more details on the supported options.
+
+```
+runtests.sh -e package com.android.osu
+runtests.sh -e class com.android.osu.NetworkConnectionTest
+```
+
+If you manually build and push the test APK to the device you can run tests using
+
+```
+adb shell am instrument -w 'com.android.osu.tests/android.support.test.runner.AndroidJUnitRunner'
+```
+
+## Adding Tests
+Tests can be added by adding classes to the src directory. JUnit4 style test cases can
+be written by simply annotating test methods with `org.junit.Test`.
+
+## Debugging Tests
+If you are trying to debug why tests are not doing what you expected, you can add android log
+statements and use logcat to view them. The beginning and end of every tests is automatically logged
+with the tag `TestRunner`.
diff --git a/packages/Osu2/tests/runtests.sh b/packages/Osu2/tests/runtests.sh
new file mode 100755
index 0000000..3513f5b
--- /dev/null
+++ b/packages/Osu2/tests/runtests.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+if [ -z $ANDROID_BUILD_TOP ]; then
+  echo "You need to source and lunch before you can use this script"
+  exit 1
+fi
+
+echo "Running tests"
+
+set -e # fail early
+
+echo "+ mmma -j32 $ANDROID_BUILD_TOP/frameworks/base/packages/Osu2/tests"
+# NOTE Don't actually run the command above since this shell doesn't inherit functions from the
+#      caller.
+make -j32 -C $ANDROID_BUILD_TOP -f build/core/main.mk MODULES-IN-frameworks-base-packages-Osu2-tests
+
+set -x # print commands
+
+adb root
+adb wait-for-device
+
+adb install -r -g "$OUT/data/app/OsuTests/OsuTests.apk"
+
+adb shell am instrument -w "$@" 'com.android.osu.tests/android.support.test.runner.AndroidJUnitRunner'
diff --git a/packages/Osu2/tests/src/com/android/osu/NetworkConnectionTest.java b/packages/Osu2/tests/src/com/android/osu/NetworkConnectionTest.java
new file mode 100644
index 0000000..2753249
--- /dev/null
+++ b/packages/Osu2/tests/src/com/android/osu/NetworkConnectionTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.osu;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.Handler;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.io.IOException;
+
+/**
+ * Unit tests for {@link com.android.osu.NetworkConnection}.
+ */
+@SmallTest
+public class NetworkConnectionTest {
+    private static final String TEST_SSID = "TEST SSID";
+    private static final String TEST_SSID_WITH_QUOTES = "\"" + TEST_SSID + "\"";
+    private static final int TEST_NETWORK_ID = 1;
+
+    @Mock Context mContext;
+    @Mock Handler mHandler;
+    @Mock WifiManager mWifiManager;
+    @Mock NetworkConnection.Callbacks mCallbacks;
+
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+        when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager);
+    }
+
+    /**
+     * Verify that an IOException will be thrown when failed to add the network.
+     *
+     * @throws Exception
+     */
+    @Test(expected = IOException.class)
+    public void networkAddFailed() throws Exception {
+        when(mWifiManager.addNetwork(any(WifiConfiguration.class))).thenReturn(-1);
+        new NetworkConnection(mContext, mHandler, WifiSsid.createFromAsciiEncoded(TEST_SSID),
+                null, mCallbacks);
+    }
+
+    /**
+     * Verify that an IOException will be thrown when failed to enable the network.
+     *
+     * @throws Exception
+     */
+    @Test(expected = IOException.class)
+    public void networkEnableFailed() throws Exception {
+        when(mWifiManager.addNetwork(any(WifiConfiguration.class))).thenReturn(TEST_NETWORK_ID);
+        when(mWifiManager.enableNetwork(eq(TEST_NETWORK_ID), eq(true))).thenReturn(false);
+        new NetworkConnection(mContext, mHandler, WifiSsid.createFromAsciiEncoded(TEST_SSID),
+                null, mCallbacks);
+    }
+
+    /**
+     * Verify that the connection is established after receiving a
+     * WifiManager.NETWORK_STATE_CHANGED_ACTION intent indicating that we are connected.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void openNetworkConnectionEstablished() throws Exception {
+        when(mWifiManager.addNetwork(any(WifiConfiguration.class))).thenReturn(TEST_NETWORK_ID);
+        when(mWifiManager.enableNetwork(eq(TEST_NETWORK_ID), eq(true))).thenReturn(true);
+        NetworkConnection connection = new NetworkConnection(mContext, mHandler,
+                WifiSsid.createFromAsciiEncoded(TEST_SSID), null, mCallbacks);
+
+        // Verify the WifiConfiguration being added.
+        ArgumentCaptor<WifiConfiguration> wifiConfig =
+                ArgumentCaptor.forClass(WifiConfiguration.class);
+        verify(mWifiManager).addNetwork(wifiConfig.capture());
+        assertEquals(wifiConfig.getValue().SSID, TEST_SSID_WITH_QUOTES);
+
+        // Capture the BroadcastReceiver.
+        ArgumentCaptor<BroadcastReceiver> receiver =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(receiver.capture(), any(), any(), any());
+
+        // Setup intent.
+        Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        NetworkInfo networkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
+        networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", "");
+        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
+        WifiInfo wifiInfo = new WifiInfo();
+        wifiInfo.setNetworkId(TEST_NETWORK_ID);
+        intent.putExtra(WifiManager.EXTRA_WIFI_INFO, wifiInfo);
+
+        // Send intent to the receiver.
+        Network network = new Network(0);
+        when(mWifiManager.getCurrentNetwork()).thenReturn(network);
+        receiver.getValue().onReceive(mContext, intent);
+
+        // Verify we are connected.
+        verify(mCallbacks).onConnected(eq(network));
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
index 653a453..81f7315 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
@@ -16,6 +16,8 @@
 
 package com.android.printspooler.model;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -29,21 +31,27 @@
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.print.PageRange;
 import android.print.PrintAttributes;
-import android.print.PrintAttributes.MediaSize;
 import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.MediaSize;
 import android.print.PrintDocumentInfo;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.View;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.printspooler.renderer.IPdfRenderer;
 import com.android.printspooler.renderer.PdfManipulationService;
 import com.android.printspooler.util.BitmapSerializeUtils;
+import com.android.printspooler.util.PageRangeUtils;
+
 import dalvik.system.CloseGuard;
+
 import libcore.io.IoUtils;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -69,8 +77,9 @@
 
     private RenderSpec mLastRenderSpec;
 
-    private int mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
-    private int mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
+    @Nullable private PageRange mScheduledPreloadVisiblePages;
+    @Nullable private PageRange[] mScheduledPreloadSelectedPages;
+    @Nullable private PageRange[] mScheduledPreloadWrittenPages;
 
     private int mState;
 
@@ -129,14 +138,24 @@
         }
     }
 
-    public void startPreload(int firstShownPage, int lastShownPage) {
+    /**
+     * Preload selected, written pages around visiblePages.
+     *
+     * @param visiblePages The pages currently visible
+     * @param selectedPages The pages currently selected (e.g. they might become visible by
+     *                      scrolling)
+     * @param writtenPages The pages currently in the document
+     */
+    public void startPreload(@NonNull PageRange visiblePages, @NonNull PageRange[] selectedPages,
+            @NonNull PageRange[] writtenPages) {
         // If we do not have a render spec we have no clue what size the
         // preloaded bitmaps should be, so just take a note for what to do.
         if (mLastRenderSpec == null) {
-            mScheduledPreloadFirstShownPage = firstShownPage;
-            mScheduledPreloadLastShownPage = lastShownPage;
+            mScheduledPreloadVisiblePages = visiblePages;
+            mScheduledPreloadSelectedPages = selectedPages;
+            mScheduledPreloadWrittenPages = writtenPages;
         } else if (mState == STATE_OPENED) {
-            mRenderer.startPreload(firstShownPage, lastShownPage, mLastRenderSpec);
+            mRenderer.startPreload(visiblePages, selectedPages, writtenPages, mLastRenderSpec);
         }
     }
 
@@ -225,11 +244,12 @@
 
             // We tired to preload but didn't know the bitmap size, now
             // that we know let us do the work.
-            if (mScheduledPreloadFirstShownPage != INVALID_PAGE_INDEX
-                    && mScheduledPreloadLastShownPage != INVALID_PAGE_INDEX) {
-                startPreload(mScheduledPreloadFirstShownPage, mScheduledPreloadLastShownPage);
-                mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
-                mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
+            if (mScheduledPreloadVisiblePages != null) {
+                startPreload(mScheduledPreloadVisiblePages, mScheduledPreloadSelectedPages,
+                        mScheduledPreloadWrittenPages);
+                mScheduledPreloadVisiblePages = null;
+                mScheduledPreloadSelectedPages = null;
+                mScheduledPreloadWrittenPages = null;
             }
 
             if (mState == STATE_OPENED) {
@@ -526,10 +546,45 @@
             mDestroyed = true;
         }
 
-        public void startPreload(int firstShownPage, int lastShownPage, RenderSpec renderSpec) {
+        /**
+         * How many pages are {@code pages} before pageNum. E.g. page 5 in [0-1], [4-7] has the
+         * index 4.
+         *
+         * @param pageNum The number of the page to find
+         * @param pages A normalized array of page ranges
+         *
+         * @return The index or {@link #INVALID_PAGE_INDEX} if not found
+         */
+        private int findIndexOfPage(int pageNum, @NonNull PageRange[] pages) {
+            int pagesBefore = 0;
+            for (int i = 0; i < pages.length; i++) {
+                if (pages[i].contains(pageNum)) {
+                    return pagesBefore + pageNum - pages[i].getStart();
+                } else {
+                    pagesBefore += pages[i].getSize();
+                }
+            }
+
+            return INVALID_PAGE_INDEX;
+        }
+
+        void startPreload(@NonNull PageRange visiblePages, @NonNull PageRange[] selectedPages,
+                @NonNull PageRange[] writtenPages, RenderSpec renderSpec) {
+            if (PageRangeUtils.isAllPages(selectedPages)) {
+                selectedPages = new PageRange[]{new PageRange(0, mPageCount - 1)};
+            }
+
             if (DEBUG) {
-                Log.i(LOG_TAG, "Preloading pages around [" + firstShownPage
-                        + "-" + lastShownPage + "]");
+                Log.i(LOG_TAG, "Preloading pages around " + visiblePages + " from "
+                        + Arrays.toString(selectedPages));
+            }
+
+            int firstVisiblePageIndex = findIndexOfPage(visiblePages.getStart(), selectedPages);
+            int lastVisiblePageIndex = findIndexOfPage(visiblePages.getEnd(), selectedPages);
+
+            if (firstVisiblePageIndex == INVALID_PAGE_INDEX
+                    || lastVisiblePageIndex == INVALID_PAGE_INDEX) {
+                return;
             }
 
             final int bitmapSizeInBytes = renderSpec.bitmapWidth * renderSpec.bitmapHeight
@@ -537,28 +592,33 @@
             final int maxCachedPageCount = mPageContentCache.getMaxSizeInBytes()
                     / bitmapSizeInBytes;
             final int halfPreloadCount = (maxCachedPageCount
-                    - (lastShownPage - firstShownPage)) / 2 - 1;
+                    - (lastVisiblePageIndex - firstVisiblePageIndex)) / 2 - 1;
 
-            final int excessFromStart;
-            if (firstShownPage - halfPreloadCount < 0) {
-                excessFromStart = halfPreloadCount - firstShownPage;
-            } else {
-                excessFromStart = 0;
+            final int fromIndex = Math.max(firstVisiblePageIndex - halfPreloadCount, 0);
+            final int toIndex = lastVisiblePageIndex + halfPreloadCount;
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "fromIndex=" + fromIndex + " toIndex=" + toIndex);
             }
 
-            final int excessFromEnd;
-            if (lastShownPage + halfPreloadCount >= mPageCount) {
-                excessFromEnd = (lastShownPage + halfPreloadCount) - mPageCount;
-            } else {
-                excessFromEnd = 0;
-            }
+            int previousRangeSizes = 0;
+            for (int rangeNum = 0; rangeNum < selectedPages.length; rangeNum++) {
+                PageRange range = selectedPages[rangeNum];
 
-            final int fromIndex = Math.max(firstShownPage - halfPreloadCount - excessFromEnd, 0);
-            final int toIndex = Math.min(lastShownPage + halfPreloadCount + excessFromStart,
-                    mPageCount - 1);
+                int thisRangeStart = Math.max(0, fromIndex - previousRangeSizes);
+                int thisRangeEnd = Math.min(range.getSize(), toIndex - previousRangeSizes + 1);
 
-            for (int i = fromIndex; i <= toIndex; i++) {
-                renderPage(i, renderSpec, null);
+                for (int i = thisRangeStart; i < thisRangeEnd; i++) {
+                    if (PageRangeUtils.contains(writtenPages, range.getStart() + i)) {
+                        if (DEBUG) {
+                            Log.i(LOG_TAG, "Preloading " + (range.getStart() + i));
+                        }
+
+                        renderPage(range.getStart() + i, renderSpec, null);
+                    }
+                }
+
+                previousRangeSizes += range.getSize();
             }
         }
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index e172948..ad46b60 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -16,6 +16,7 @@
 
 package com.android.printspooler.ui;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -24,8 +25,8 @@
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.print.PageRange;
-import android.print.PrintAttributes.MediaSize;
 import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.MediaSize;
 import android.print.PrintDocumentInfo;
 import android.support.v7.widget.RecyclerView.Adapter;
 import android.support.v7.widget.RecyclerView.ViewHolder;
@@ -33,11 +34,12 @@
 import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.MeasureSpec;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
-import android.view.View.MeasureSpec;
 import android.widget.TextView;
+
 import com.android.printspooler.R;
 import com.android.printspooler.model.OpenDocumentCallback;
 import com.android.printspooler.model.PageContentRepository;
@@ -45,6 +47,7 @@
 import com.android.printspooler.util.PageRangeUtils;
 import com.android.printspooler.widget.PageContentView;
 import com.android.printspooler.widget.PreviewPageFrame;
+
 import dalvik.system.CloseGuard;
 
 import java.util.ArrayList;
@@ -797,14 +800,16 @@
         page.setTag(null);
     }
 
-    public void startPreloadContent(PageRange pageRangeInAdapter) {
-        final int startPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getStart());
-        final int startPageInFile = computePageIndexInFile(startPageInDocument);
-        final int endPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getEnd());
-        final int endPageInFile = computePageIndexInFile(endPageInDocument);
-        if (startPageInDocument != INVALID_PAGE_INDEX && endPageInDocument != INVALID_PAGE_INDEX) {
-            mPageContentRepository.startPreload(startPageInFile, endPageInFile);
+    void startPreloadContent(@NonNull PageRange visiblePagesInAdapter) {
+        int startVisibleDocument = computePageIndexInDocument(visiblePagesInAdapter.getStart());
+        int endVisibleDocument = computePageIndexInDocument(visiblePagesInAdapter.getEnd());
+        if (startVisibleDocument == INVALID_PAGE_INDEX
+                || endVisibleDocument == INVALID_PAGE_INDEX) {
+            return;
         }
+
+        mPageContentRepository.startPreload(new PageRange(startVisibleDocument, endVisibleDocument),
+                mSelectedPages, mWrittenPages);
     }
 
     public void stopPreloadContent() {
diff --git a/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml b/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml
index 4aa8569..4b81a3c 100644
--- a/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml
+++ b/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml
@@ -18,7 +18,7 @@
     android:height="24dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
-    android:tint="?android:attr/colorAccent">
+    android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FF000000"
         android:pathData="M20,5L4,5c-1.1,0 -1.99,0.9 -1.99,2L2,17c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9
diff --git a/packages/SettingsLib/res/layout/preference_two_target.xml b/packages/SettingsLib/res/layout/preference_two_target.xml
index 69b7204..7000940d 100644
--- a/packages/SettingsLib/res/layout/preference_two_target.xml
+++ b/packages/SettingsLib/res/layout/preference_two_target.xml
@@ -32,7 +32,8 @@
         android:background="?android:attr/selectableItemBackground"
         android:gravity="start|center_vertical"
         android:clipToPadding="false"
-        android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
 
         <LinearLayout
             android:id="@+id/icon_frame"
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index fbdb394..b4c8cec 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -282,7 +282,7 @@
     <string name="show_all_anrs" msgid="28462979638729082">"Tots els errors sense resposta"</string>
     <string name="show_all_anrs_summary" msgid="641908614413544127">"Informa que una aplicació en segon pla no respon"</string>
     <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Mostra avisos del canal de notificacions"</string>
-    <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Mostra un avís a la pantalla quan una app publica una notificació sense canal vàlid"</string>
+    <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Mostra un avís a la pantalla quan una aplicació publica una notificació sense un canal vàlid"</string>
     <string name="force_allow_on_external" msgid="3215759785081916381">"Força permís d\'aplicacions a l\'emmagatzem. extern"</string>
     <string name="force_allow_on_external_summary" msgid="3640752408258034689">"Permet que qualsevol aplicació es pugui escriure en un dispositiu d’emmagatzematge extern, independentment dels valors definits"</string>
     <string name="force_resizable_activities" msgid="8615764378147824985">"Força l\'ajust de la mida de les activitats"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 66e55f9..2949012 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -28,7 +28,7 @@
     <string name="wifi_disabled_by_recommendation_provider" msgid="5168315140978066096">"No se estableció conexión debido a la mala calidad de la red"</string>
     <string name="wifi_disabled_wifi_failure" msgid="3081668066612876581">"Error de conexión Wi-Fi"</string>
     <string name="wifi_disabled_password_failure" msgid="8659805351763133575">"Problema de autenticación"</string>
-    <string name="wifi_cant_connect" msgid="5410016875644565884">"No se puede establecer conexión"</string>
+    <string name="wifi_cant_connect" msgid="5410016875644565884">"No se puede establecer la conexión"</string>
     <string name="wifi_cant_connect_to_ap" msgid="1222553274052685331">"No se puede establecer conexión con \"<xliff:g id="AP_NAME">%1$s</xliff:g>\""</string>
     <string name="wifi_check_password_try_again" msgid="516958988102584767">"Revisa la contraseña y vuelve a intentarlo"</string>
     <string name="wifi_not_in_range" msgid="1136191511238508967">"Fuera de alcance"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 414ff31..736579f 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -281,8 +281,8 @@
     <string name="app_process_limit_title" msgid="4280600650253107163">"Limite proc. em 2º plano"</string>
     <string name="show_all_anrs" msgid="28462979638729082">"Mostrar todos os ANR"</string>
     <string name="show_all_anrs_summary" msgid="641908614413544127">"Mostrar erro \"Aplic. não Resp.\" p/ aplic. 2º plano"</string>
-    <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Mostrar avisos do canal de notif."</string>
-    <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Mostra um aviso no ecrã quando uma aplic. publica uma notific. sem um canal válido"</string>
+    <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Mostrar avisos do canal de notificações"</string>
+    <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Mostra um aviso no ecrã quando uma aplicação publica uma notificação sem o canal ser válido"</string>
     <string name="force_allow_on_external" msgid="3215759785081916381">"Forçar perm. de aplicações no armazenamento ext."</string>
     <string name="force_allow_on_external_summary" msgid="3640752408258034689">"Torna qualquer aplicação elegível para ser gravada no armazenamento externo, independentemente dos valores do manifesto"</string>
     <string name="force_resizable_activities" msgid="8615764378147824985">"Forçar as atividades a serem redimensionáveis"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index d29d8d4..edd02c9 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -88,7 +88,7 @@
     <string name="bluetooth_pairing_decline" msgid="4185420413578948140">"取消"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"配對完成後,所配對的裝置即可在連線後存取你的聯絡人和通話紀錄。"</string>
     <string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 配對。"</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 配對,因為 PIN 或密碼金鑰不正確。"</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 配對,因為 PIN 碼或密碼金鑰不正確。"</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 通訊。"</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」拒絕配對要求。"</string>
     <string name="accessibility_wifi_off" msgid="1166761729660614716">"已關閉 Wi-Fi。"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index b21f2fa..5767823 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -35,7 +35,7 @@
     private static String sServicesSystemSharedLibPackageName;
     private static String sSharedSystemSharedLibPackageName;
 
-    public static final int[] WIFI_PIE_FOR_BADGING = {
+    static final int[] WIFI_PIE_FOR_BADGING = {
           com.android.internal.R.drawable.ic_signal_wifi_badged_0_bars,
           com.android.internal.R.drawable.ic_signal_wifi_badged_1_bar,
           com.android.internal.R.drawable.ic_signal_wifi_badged_2_bars,
@@ -294,12 +294,7 @@
                 });
     }
 
-    /**
-     * Returns the resource id for the given badge or {@link View.NO_ID} if no badge is to be shown.
-     *
-     * @throws IllegalArgumentException if the given badge value is not supported.
-     */
-    public static int getWifiBadgeResource(int badge) {
+    private static int getWifiBadgeResource(int badge) {
         switch (badge) {
             case NetworkBadging.BADGING_NONE:
                 return View.NO_ID;
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 474de90..1cbb745 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -88,21 +88,16 @@
     private static final String XMLTAG_TIMEZONE = "timezone";
 
     public static CharSequence getTimeZoneOffsetAndName(Context context, TimeZone tz, Date now) {
-        final Locale locale = Locale.getDefault();
-        final CharSequence gmtText = getGmtOffsetText(context, locale, tz, now);
-        final TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
-        final ZoneGetterData data = new ZoneGetterData(context);
-
-        final boolean useExemplarLocationForLocalNames =
-                shouldUseExemplarLocationForLocalNames(data, timeZoneNames);
-        final CharSequence zoneName = getTimeZoneDisplayName(data, timeZoneNames,
-                useExemplarLocationForLocalNames, tz, tz.getID());
-        if (zoneName == null) {
+        Locale locale = Locale.getDefault();
+        CharSequence gmtText = getGmtOffsetText(context, locale, tz, now);
+        TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
+        String zoneNameString = getZoneLongName(timeZoneNames, tz, now);
+        if (zoneNameString == null) {
             return gmtText;
         }
 
         // We don't use punctuation here to avoid having to worry about localizing that too!
-        return TextUtils.concat(gmtText, " ", zoneName);
+        return TextUtils.concat(gmtText, " ", zoneNameString);
     }
 
     public static List<Map<String, Object>> getZonesList(Context context) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index f52a6b5..04a3d1f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -165,6 +165,9 @@
      * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the
      * custom view which should be displayed for the preference. The custom view will be inflated
      * as a remote view.
+     *
+     * This also can be used with {@link META_DATA_PREFERENCE_SUMMARY_URI} above, by setting the id
+     * of the summary TextView to '@android:id/summary'.
      */
     public static final String META_DATA_PREFERENCE_CUSTOM_VIEW =
             "com.android.settings.custom_view";
@@ -315,6 +318,7 @@
         PackageManager pm = context.getPackageManager();
         List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
                 PackageManager.GET_META_DATA, user.getIdentifier());
+        Map<String, IContentProvider> providerMap = new HashMap<>();
         for (ResolveInfo resolved : results) {
             if (!resolved.system) {
                 // Do not allow any app to add to settings, only system ones.
@@ -346,7 +350,7 @@
                 tile.priority = usePriority ? resolved.priority : 0;
                 tile.metaData = activityInfo.metaData;
                 updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
-                        pm);
+                        pm, providerMap);
                 if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
 
                 addedCache.put(key, tile);
@@ -361,14 +365,14 @@
     }
 
     private static boolean updateTileData(Context context, Tile tile,
-            ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm) {
+            ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm,
+            Map<String, IContentProvider> providerMap) {
         if (applicationInfo.isSystemApp()) {
             int icon = 0;
             Pair<String, Integer> iconFromUri = null;
             CharSequence title = null;
             String summary = null;
             String keyHint = null;
-            Uri uri = null;
             RemoteViews remoteViews = null;
 
             // Get the activity's meta-data
@@ -414,6 +418,15 @@
                     if (metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)) {
                         int layoutId = metaData.getInt(META_DATA_PREFERENCE_CUSTOM_VIEW);
                         remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
+                        if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
+                            String uriString = metaData.getString(
+                                    META_DATA_PREFERENCE_SUMMARY_URI);
+                            String overrideSummary = getTextFromUri(context, uriString, providerMap,
+                                    META_DATA_PREFERENCE_SUMMARY);
+                            if (overrideSummary != null) {
+                                remoteViews.setTextViewText(android.R.id.summary, overrideSummary);
+                            }
+                        }
                     }
                 }
             } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 0f9b2ff..edb3226 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -540,7 +540,10 @@
             }
         }
 
-        mSeen = seen;
+        // Only replace the previous value if we have a recent scan result to use
+        if (seen != 0) {
+            mSeen = seen;
+        }
     }
 
     /**
@@ -984,8 +987,10 @@
         security = getSecurity(result);
         if (security == SECURITY_PSK)
             pskType = getPskType(result);
-        mRssi = result.level;
-        mSeen = result.timestamp;
+
+        mScanResultCache.put(result.BSSID, result);
+        updateRssi();
+        mSeen = result.timestamp; // even if the timestamp is old it is still valid
     }
 
     public void saveWifiState(Bundle savedState) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index ec94841..0d67ad0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -12,17 +12,13 @@
 
 import android.content.Intent;
 import android.net.NetworkInfo;
-import android.net.NetworkKey;
-import android.net.WifiKey;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.util.Log;
 
 import java.util.List;
 
 public class WifiStatusTracker {
-    private static final String TAG = "WifiStatusTracker";
 
     private final WifiManager mWifiManager;
     public boolean enabled;
@@ -32,7 +28,6 @@
     public String ssid;
     public int rssi;
     public int level;
-    public NetworkKey networkKey;
 
     public WifiStatusTracker(WifiManager wifiManager) {
         mWifiManager = wifiManager;
@@ -54,32 +49,19 @@
             connecting = networkInfo != null && !networkInfo.isConnected()
                     && networkInfo.isConnectedOrConnecting();
             connected = networkInfo != null && networkInfo.isConnected();
-            WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
-                    ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
-                    : mWifiManager.getConnectionInfo();
-
             // If Connected grab the signal strength and ssid.
-            if (connected && info != null) {
-                ssid = getSsid(info);
-                String bssid = info.getBSSID();
-                if ((ssid != null) && (bssid != null)) {
-                    // Reuse existing network key object if possible.
-                    if ((networkKey == null)
-                            || !networkKey.wifiKey.ssid.equals(ssid)
-                            || !networkKey.wifiKey.bssid.equals(bssid)) {
-                        try {
-                            networkKey = new NetworkKey(
-                                    new WifiKey(ssid, bssid));
-                        } catch (IllegalArgumentException e) {
-                            Log.e(TAG, "Cannot create NetworkKey", e);
-                        }
-                    }
+            if (connected) {
+                // try getting it out of the intent first
+                WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
+                        ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
+                        : mWifiManager.getConnectionInfo();
+                if (info != null) {
+                    ssid = getSsid(info);
                 } else {
-                    networkKey = null;
+                    ssid = null;
                 }
-            } else {
+            } else if (!connected) {
                 ssid = null;
-                networkKey = null;
             }
         } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
             // Default to -200 as its below WifiManager.MIN_RSSI.
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 20cc5a6..9083d90 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -144,6 +144,7 @@
 
     @VisibleForTesting
     Scanner mScanner;
+    private boolean mStaleScanResults = false;
 
     public WifiTracker(Context context, WifiListener wifiListener,
             boolean includeSaved, boolean includeScans) {
@@ -348,7 +349,11 @@
      * Stop tracking wifi networks and scores.
      *
      * <p>This should always be called when done with a WifiTracker (if startTracking was called) to
-     * ensure proper cleanup and prevent any further callbacks from occuring.
+     * ensure proper cleanup and prevent any further callbacks from occurring.
+     *
+     * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
+     * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
+     * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
      */
     @MainThread
     public void stopTracking() {
@@ -365,6 +370,7 @@
             mWorkHandler.removePendingMessages();
             mMainHandler.removePendingMessages();
         }
+        mStaleScanResults = true;
     }
 
     private void unregisterAndClearScoreCache() {
@@ -730,6 +736,11 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
+
+            if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
+                mStaleScanResults = false;
+            }
+
             if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
                 updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
                         WifiManager.WIFI_STATE_UNKNOWN));
@@ -840,7 +851,9 @@
 
             switch (msg.what) {
                 case MSG_UPDATE_ACCESS_POINTS:
-                    updateAccessPointsLocked();
+                    if (!mStaleScanResults) {
+                        updateAccessPointsLocked();
+                    }
                     break;
                 case MSG_UPDATE_NETWORK_INFO:
                     updateNetworkInfo((NetworkInfo) msg.obj);
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java
index 703e9d2..a3345ee 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java
@@ -47,9 +47,9 @@
     }
 
     @Test
-    public void getTimeZoneOffsetAndName_setLondon_returnLondon() {
-        // Check it will ends with 'London', not 'British Summer Time' or sth else
-        testTimeZoneOffsetAndNameInner(TIME_ZONE_LONDON_ID, "London");
+    public void getTimeZoneOffsetAndName_setLondon_returnBritishSummerTime() {
+        // Check it will ends with 'British Summer Time', not 'London' or sth else
+        testTimeZoneOffsetAndNameInner(TIME_ZONE_LONDON_ID, "British Summer Time");
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index 46ea319..340ef01 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -747,4 +748,36 @@
 
         verifyNoMoreInteractions(mockWifiListener);
     }
+
+    @Test
+    public void stopTrackingShouldSetStaleBitWhichPreventsCallbacksUntilNextScanResult()
+            throws Exception {
+        WifiTracker tracker = createMockedWifiTracker();
+        startTracking(tracker);
+        tracker.stopTracking();
+
+        CountDownLatch latch1 = new CountDownLatch(1);
+        tracker.mMainHandler.post(() -> {
+                latch1.countDown();
+        });
+        assertTrue("Latch 1 timed out", latch1.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+
+        startTracking(tracker);
+
+        tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
+        tracker.mReceiver.onReceive(
+                mContext, new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION));
+        tracker.mReceiver.onReceive(
+                mContext, new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION));
+
+        CountDownLatch latch2 = new CountDownLatch(1);
+        tracker.mMainHandler.post(() -> {
+            latch2.countDown();
+        });
+        assertTrue("Latch 2 timed out", latch2.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+
+        verify(mockWifiListener, never()).onAccessPointsChanged();
+
+        sendScanResultsAndProcess(tracker); // verifies onAccessPointsChanged is invoked
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index a2e0e2c..7cfb32d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -36,6 +36,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Pair;
+import android.widget.RemoteViews;
 
 import com.android.settingslib.R;
 import com.android.settingslib.TestConfig;
@@ -51,6 +52,7 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
+import org.robolectric.internal.ShadowExtractor;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -68,9 +70,13 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+@Config(manifest = TestConfig.MANIFEST_PATH,
+        sdk = TestConfig.SDK_VERSION,
+        shadows = {TileUtilsTest.TileUtilsShadowRemoteViews.class})
 public class TileUtilsTest {
 
     @Mock
@@ -364,6 +370,86 @@
         assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
     }
 
+    @Test
+    public void getTilesForIntent_summaryUriSpecified_shouldOverrideRemoteViewSummary()
+            throws RemoteException {
+        Intent intent = new Intent();
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        ResolveInfo resolveInfo = newInfo(true, null /* category */, null,
+                null, URI_GET_SUMMARY);
+        resolveInfo.activityInfo.metaData.putInt("com.android.settings.custom_view",
+                R.layout.user_preference);
+        info.add(resolveInfo);
+
+        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+                .thenReturn(info);
+
+        // Mock the content provider interaction.
+        Bundle bundle = new Bundle();
+        bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY, "new summary text");
+        when(mIContentProvider.call(anyString(),
+                eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_SUMMARY))), eq(URI_GET_SUMMARY),
+                any())).thenReturn(bundle);
+        when(mContentResolver.acquireUnstableProvider(anyString()))
+                .thenReturn(mIContentProvider);
+        when(mContentResolver.acquireUnstableProvider(any(Uri.class)))
+                .thenReturn(mIContentProvider);
+
+        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+                null /* defaultCategory */, outTiles, false /* usePriority */,
+                false /* checkCategory */);
+
+        assertThat(outTiles.size()).isEqualTo(1);
+        Tile tile = outTiles.get(0);
+        assertThat(tile.remoteViews).isNotNull();
+        assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
+        // Make sure the summary TextView got a new text string.
+        TileUtilsShadowRemoteViews shadowRemoteViews =
+                (TileUtilsShadowRemoteViews) ShadowExtractor.extract(tile.remoteViews);
+        assertThat(shadowRemoteViews.overrideViewId).isEqualTo(android.R.id.summary);
+        assertThat(shadowRemoteViews.overrideText).isEqualTo("new summary text");
+    }
+
+    @Test
+    public void getTilesForIntent_providerUnavailable_shouldNotOverrideRemoteViewSummary()
+            throws RemoteException {
+        Intent intent = new Intent();
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        ResolveInfo resolveInfo = newInfo(true, null /* category */, null,
+                null, URI_GET_SUMMARY);
+        resolveInfo.activityInfo.metaData.putInt("com.android.settings.custom_view",
+                R.layout.user_preference);
+        info.add(resolveInfo);
+
+        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+                .thenReturn(info);
+
+        // Mock the content provider interaction.
+        Bundle bundle = new Bundle();
+        bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY, "new summary text");
+        when(mIContentProvider.call(anyString(),
+                eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_SUMMARY))), eq(URI_GET_SUMMARY),
+                any())).thenReturn(bundle);
+
+        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+                null /* defaultCategory */, outTiles, false /* usePriority */,
+                false /* checkCategory */);
+
+        assertThat(outTiles.size()).isEqualTo(1);
+        Tile tile = outTiles.get(0);
+        assertThat(tile.remoteViews).isNotNull();
+        assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
+        // Make sure the summary TextView didn't get any text view updates.
+        TileUtilsShadowRemoteViews shadowRemoteViews =
+                (TileUtilsShadowRemoteViews) ShadowExtractor.extract(tile.remoteViews);
+        assertThat(shadowRemoteViews.overrideViewId).isNull();
+        assertThat(shadowRemoteViews.overrideText).isNull();
+    }
+
     public static ResolveInfo newInfo(boolean systemApp, String category) {
         return newInfo(systemApp, category, null);
     }
@@ -423,4 +509,17 @@
             info.activityInfo.metaData.putString(key, value);
         }
     }
+
+    @Implements(RemoteViews.class)
+    public static class TileUtilsShadowRemoteViews {
+
+        private Integer overrideViewId;
+        private CharSequence overrideText;
+
+        @Implementation
+        public void setTextViewText(int viewId, CharSequence text) {
+            overrideViewId = viewId;
+            overrideText = text;
+        }
+    }
 }
diff --git a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java
index 4a5d8b4..2d794fb 100644
--- a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java
+++ b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.support.annotation.NonNull;
 import android.support.annotation.VisibleForTesting;
+import android.support.v4.graphics.ColorUtils;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -41,15 +42,12 @@
 
     private static final String TAG = "ColorExtractor";
 
-    @VisibleForTesting
-    static final int FALLBACK_COLOR = 0xff83888d;
+    public static final int FALLBACK_COLOR = 0xff83888d;
 
     private int mMainFallbackColor = FALLBACK_COLOR;
     private int mSecondaryFallbackColor = FALLBACK_COLOR;
     private final SparseArray<GradientColors[]> mGradientColors;
     private final ArrayList<OnColorsChangedListener> mOnColorsChangedListeners;
-    // Colors to return when the wallpaper isn't visible
-    private final GradientColors mWpHiddenColors;
     private final Context mContext;
     private final ExtractionType mExtractionType;
 
@@ -60,9 +58,6 @@
     @VisibleForTesting
     public ColorExtractor(Context context, ExtractionType extractionType) {
         mContext = context;
-        mWpHiddenColors = new GradientColors();
-        mWpHiddenColors.setMainColor(FALLBACK_COLOR);
-        mWpHiddenColors.setSecondaryColor(FALLBACK_COLOR);
         mExtractionType = extractionType;
 
         mGradientColors = new SparseArray<>();
@@ -123,7 +118,6 @@
         if (which != WallpaperManager.FLAG_LOCK && which != WallpaperManager.FLAG_SYSTEM) {
             throw new IllegalArgumentException("which should be FLAG_SYSTEM or FLAG_NORMAL");
         }
-
         return mGradientColors.get(which)[type];
     }
 
@@ -134,7 +128,6 @@
             GradientColors[] lockColors = mGradientColors.get(WallpaperManager.FLAG_LOCK);
             extractInto(colors, lockColors[TYPE_NORMAL], lockColors[TYPE_DARK],
                     lockColors[TYPE_EXTRA_DARK]);
-
             changed = true;
         }
         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
@@ -149,7 +142,7 @@
         }
     }
 
-    private void triggerColorsChanged(int which) {
+    protected void triggerColorsChanged(int which) {
         for (OnColorsChangedListener listener: mOnColorsChangedListeners) {
             listener.onColorsChanged(this, which);
         }
@@ -258,4 +251,4 @@
     public interface OnColorsChangedListener {
         void onColorsChanged(ColorExtractor colorExtractor, int which);
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java
index 5b4b3ed..81bc831 100644
--- a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java
+++ b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java
@@ -29,6 +29,8 @@
 
 import com.google.android.colorextraction.ColorExtractor.GradientColors;
 
+import java.util.List;
+
 /**
  * Implementation of tonal color extraction
  */
@@ -40,9 +42,6 @@
     private static final float FIT_WEIGHT_S = 1.0f;
     private static final float FIT_WEIGHT_L = 10.0f;
 
-    // When extracting the main color, only consider colors
-    // present in at least MIN_COLOR_OCCURRENCE of the image
-    private static final float MIN_COLOR_OCCURRENCE = 0.1f;
     private static final boolean DEBUG = true;
 
     // Temporary variable to avoid allocations
@@ -61,7 +60,10 @@
             @NonNull GradientColors outColorsNormal, @NonNull GradientColors outColorsDark,
             @NonNull GradientColors outColorsExtraDark) {
 
-        if (inWallpaperColors.getColors().size() == 0) {
+        final List<Color> mainColors = inWallpaperColors.getMainColors();
+        final int mainColorsSize = mainColors.size();
+
+        if (mainColorsSize == 0) {
             return false;
         }
         // Tonal is not really a sort, it takes a color from the extracted
@@ -69,30 +71,18 @@
         // palettes. The best fit is tweaked to be closer to the source color
         // and replaces the original palette
 
-        // First find the most representative color in the image
-        populationSort(inWallpaperColors);
-        // Calculate total
-        int total = 0;
-        for (Pair<Color, Integer> weightedColor : inWallpaperColors.getColors()) {
-            total += weightedColor.second;
-        }
-
-        // Get bright colors that occur often enough in this image
-        Pair<Color, Integer> bestColor = null;
-        float[] hsl = new float[3];
-        for (Pair<Color, Integer> weightedColor : inWallpaperColors.getColors()) {
-            float colorOccurrence = weightedColor.second / (float) total;
-            if (colorOccurrence < MIN_COLOR_OCCURRENCE) {
-                break;
-            }
-
-            int colorValue = weightedColor.first.toArgb();
+        // Get the most preeminent, non-blacklisted color.
+        Color bestColor = null;
+        final float[] hsl = new float[3];
+        for (int i = 0; i < mainColorsSize; i++) {
+            final Color color = mainColors.get(i);
+            final int colorValue = color.toArgb();
             ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue),
                     Color.blue(colorValue), hsl);
 
             // Stop when we find a color that meets our criteria
             if (!isBlacklisted(hsl)) {
-                bestColor = weightedColor;
+                bestColor = color;
                 break;
             }
         }
@@ -102,7 +92,7 @@
             return false;
         }
 
-        int colorValue = bestColor.first.toArgb();
+        int colorValue = bestColor.toArgb();
         ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue), Color.blue(colorValue),
                 hsl);
 
@@ -212,10 +202,6 @@
         return false;
     }
 
-    private static void populationSort(@NonNull WallpaperColors wallpaperColors) {
-        wallpaperColors.getColors().sort((a, b) -> b.second - a.second);
-    }
-
     /**
      * Offsets all colors by a delta, clamping values that go beyond what's
      * supported on the color space.
@@ -269,7 +255,9 @@
         TonalPalette best = null;
         float error = Float.POSITIVE_INFINITY;
 
-        for (TonalPalette candidate : TONAL_PALETTES) {
+        for (int i = 0; i < TONAL_PALETTES.length; i++) {
+            final TonalPalette candidate = TONAL_PALETTES[i];
+
             if (h >= candidate.minHue && h <= candidate.maxHue) {
                 best = candidate;
                 break;
diff --git a/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java b/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java
index fd698d0..b5f4a8c 100644
--- a/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java
+++ b/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java
@@ -39,7 +39,7 @@
 import org.junit.runner.RunWith;
 
 /**
- * Tests tonal palette generation.
+ * Tests color extraction generation.
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -101,14 +101,12 @@
         };
         ColorExtractor extractor = new ColorExtractor(mContext, type);
 
-        assertEquals("Extracted colors not being used!",
-                extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_NORMAL),
-                colorsExpectedNormal);
-        assertEquals("Extracted colors not being used!",
-                extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_DARK),
-                colorsExpectedDark);
-        assertEquals("Extracted colors not being used!",
-                extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_EXTRA_DARK),
-                colorsExpectedExtraDark);
+        GradientColors colors = extractor.getColors(WallpaperManager.FLAG_SYSTEM,
+                ColorExtractor.TYPE_NORMAL);
+        assertEquals("Extracted colors not being used!", colors, colorsExpectedNormal);
+        colors = extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_DARK);
+        assertEquals("Extracted colors not being used!", colors, colorsExpectedDark);
+        colors = extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_EXTRA_DARK);
+        assertEquals("Extracted colors not being used!", colors, colorsExpectedExtraDark);
     }
 }
diff --git a/packages/SystemUI/res/layout/tv_pip_controls.xml b/packages/SystemUI/res/layout/tv_pip_controls.xml
index 61ac6f69..0b7bce1 100644
--- a/packages/SystemUI/res/layout/tv_pip_controls.xml
+++ b/packages/SystemUI/res/layout/tv_pip_controls.xml
@@ -22,24 +22,24 @@
 
     <com.android.systemui.pip.tv.PipControlButtonView
         android:id="@+id/full_button"
-        android:layout_width="100dp"
+        android:layout_width="@dimen/picture_in_picture_button_width"
         android:layout_height="wrap_content"
         android:src="@drawable/ic_fullscreen_white_24dp"
         android:text="@string/pip_fullscreen" />
 
     <com.android.systemui.pip.tv.PipControlButtonView
         android:id="@+id/close_button"
-        android:layout_width="100dp"
+        android:layout_width="@dimen/picture_in_picture_button_width"
         android:layout_height="wrap_content"
-        android:layout_marginStart="-50dp"
+        android:layout_marginStart="@dimen/picture_in_picture_button_start_margin"
         android:src="@drawable/ic_close_white"
         android:text="@string/pip_close" />
 
     <com.android.systemui.pip.tv.PipControlButtonView
         android:id="@+id/play_pause_button"
-        android:layout_width="100dp"
+        android:layout_width="@dimen/picture_in_picture_button_width"
         android:layout_height="wrap_content"
-        android:layout_marginStart="-50dp"
+        android:layout_marginStart="@dimen/picture_in_picture_button_start_margin"
         android:src="@drawable/ic_pause_white"
         android:text="@string/pip_pause"
         android:visibility="gone" />
diff --git a/packages/SystemUI/res/layout/tv_pip_custom_control.xml b/packages/SystemUI/res/layout/tv_pip_custom_control.xml
new file mode 100644
index 0000000..dd0fce4
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_pip_custom_control.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<com.android.systemui.pip.tv.PipControlButtonView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/picture_in_picture_button_width"
+    android:layout_height="wrap_content"
+    android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" />
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index c3a9eeb..3974454 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -555,7 +555,7 @@
     <string name="notification_channel_disabled" msgid="2139193533791840539">"Aurrerantzean ez duzu jasoko horrelako jakinarazpenik"</string>
     <string name="notification_num_channels" msgid="2048144408999179471">"Jakinarazpenen <xliff:g id="NUMBER">%d</xliff:g> kategoria"</string>
     <string name="notification_default_channel_desc" msgid="2506053815870808359">"Aplikazio honek ez du jakinarazpen-kategoriarik"</string>
-    <string name="notification_unblockable_desc" msgid="3561016061737896906">"Aplikazio honen jakinarazpenak ezin dira desaktibatu"</string>
+    <string name="notification_unblockable_desc" msgid="3561016061737896906">"Ezin dira desaktibatu aplikazio honen jakinarazpenak"</string>
     <plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
       <item quantity="other">Aplikazio honen 1/<xliff:g id="NUMBER_1">%d</xliff:g> jakinarazpen-kategoria</item>
       <item quantity="one">Aplikazio honen 1/<xliff:g id="NUMBER_0">%d</xliff:g> jakinarazpen-kategoria</item>
diff --git a/packages/SystemUI/res/values-tvdpi/dimens.xml b/packages/SystemUI/res/values-tvdpi/dimens.xml
index 5327cee..4d978aa 100644
--- a/packages/SystemUI/res/values-tvdpi/dimens.xml
+++ b/packages/SystemUI/res/values-tvdpi/dimens.xml
@@ -24,4 +24,8 @@
     <fraction name="battery_subpixel_smoothing_right">10%</fraction>
 
     <dimen name="battery_margin_bottom">1px</dimen>
+
+    <!-- The dimensions to user for picture-in-picture action buttons. -->
+    <dimen name="picture_in_picture_button_width">100dp</dimen>
+    <dimen name="picture_in_picture_button_start_margin">-50dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 3ef28bd..0ab44ed 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -116,11 +116,11 @@
     <color name="segmented_buttons_background">#14FFFFFF</color><!-- 8% white -->
 
     <color name="dark_mode_icon_color_single_tone">#99000000</color>
-    <color name="dark_mode_icon_color_dual_tone_background">#3d000000</color>
+    <color name="dark_mode_icon_color_dual_tone_background">#4d000000</color>
     <color name="dark_mode_icon_color_dual_tone_fill">#7a000000</color>
 
     <color name="light_mode_icon_color_single_tone">#ffffff</color>
-    <color name="light_mode_icon_color_dual_tone_background">#4dffffff</color>
+    <color name="light_mode_icon_color_dual_tone_background">#5dffffff</color>
     <color name="light_mode_icon_color_dual_tone_fill">#ffffff</color>
 
     <color name="volume_settings_icon_color">#7fffffff</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e3a84d3..05678e2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -824,5 +824,7 @@
 
     <!-- Intended corner radius when drawing the mobile signal -->
     <dimen name="stat_sys_mobile_signal_corner_radius">0.75dp</dimen>
+    <!-- How far to inset the rounded edges -->
+    <dimen name="stat_sys_mobile_signal_circle_inset">0.9dp</dimen>
 
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
index 954fe5b..ce3068d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
@@ -26,6 +26,7 @@
 import android.view.View;
 import android.widget.Button;
 import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionInfo;
 import android.telephony.euicc.EuiccManager;
 import android.util.Log;
 
@@ -84,8 +85,11 @@
     public static boolean isEsimLocked(Context context, int subId) {
         EuiccManager euiccManager =
                 (EuiccManager) context.getSystemService(Context.EUICC_SERVICE);
-        return euiccManager.isEnabled()
-                && SubscriptionManager.from(context).getActiveSubscriptionInfo(subId).isEmbedded();
+        if (!euiccManager.isEnabled()) {
+            return false;
+        }
+        SubscriptionInfo sub = SubscriptionManager.from(context).getActiveSubscriptionInfo(subId);
+        return  sub != null && sub.isEmbedded();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 73a2a43..1b694b3 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -28,6 +28,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.PluginDependencyProvider;
@@ -38,9 +39,9 @@
 import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
-import com.android.systemui.statusbar.phone.StatusBarWindowManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
+import com.android.systemui.statusbar.phone.StatusBarWindowManager;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -77,7 +78,6 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
-import com.android.systemui.tuner.TunablePadding;
 import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerServiceImpl;
@@ -86,8 +86,6 @@
 import com.android.systemui.util.leak.LeakReporter;
 import com.android.systemui.volume.VolumeDialogControllerImpl;
 
-import com.google.android.colorextraction.ColorExtractor;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.HashMap;
@@ -109,6 +107,7 @@
  * services, registered receivers, etc.
  */
 public class Dependency extends SystemUI {
+    private static final String TAG = "Dependency";
 
     /**
      * Key for getting a background Looper for background work.
@@ -268,7 +267,7 @@
         mProviders.put(AccessibilityManagerWrapper.class,
                 () -> new AccessibilityManagerWrapper(mContext));
 
-        mProviders.put(ColorExtractor.class, () -> new ColorExtractor(mContext));
+        mProviders.put(SysuiColorExtractor.class, () -> new SysuiColorExtractor(mContext));
 
         mProviders.put(TunablePaddingService.class, () -> new TunablePaddingService());
 
diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
index c4740af..b35efb2 100644
--- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
+++ b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
@@ -118,7 +118,8 @@
                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
                 ,
                 PixelFormat.TRANSLUCENT);
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
+                | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT;
         lp.setTitle("RoundedOverlay");
         lp.gravity = Gravity.TOP;
         return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
new file mode 100644
index 0000000..5f393d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -0,0 +1,134 @@
+/*
+ * 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.colorextraction;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Display;
+import android.view.IWallpaperVisibilityListener;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.android.colorextraction.ColorExtractor;
+import com.google.android.colorextraction.types.ExtractionType;
+import com.google.android.colorextraction.types.Tonal;
+
+/**
+ * ColorExtractor aware of wallpaper visibility
+ */
+public class SysuiColorExtractor extends ColorExtractor {
+    private static final String TAG = "SysuiColorExtractor";
+    private boolean mWallpaperVisible;
+    // Colors to return when the wallpaper isn't visible
+    private final GradientColors mWpHiddenColors;
+
+    public SysuiColorExtractor(Context context) {
+        this(context, new Tonal(), true);
+    }
+
+    @VisibleForTesting
+    public SysuiColorExtractor(Context context, ExtractionType type, boolean registerVisibility) {
+        super(context, type);
+
+        mWpHiddenColors = new GradientColors();
+        mWpHiddenColors.setMainColor(FALLBACK_COLOR);
+        mWpHiddenColors.setSecondaryColor(FALLBACK_COLOR);
+
+        if (registerVisibility) {
+            try {
+                IWindowManager windowManagerService = WindowManagerGlobal.getWindowManagerService();
+                Handler handler = Handler.getMain();
+                boolean visible = windowManagerService.registerWallpaperVisibilityListener(
+                        new IWallpaperVisibilityListener.Stub() {
+                            @Override
+                            public void onWallpaperVisibilityChanged(boolean newVisibility,
+                                    int displayId) throws RemoteException {
+                                handler.post(() -> setWallpaperVisible(newVisibility));
+                            }
+                        }, Display.DEFAULT_DISPLAY);
+                setWallpaperVisible(visible);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Can't listen to wallpaper visibility changes", e);
+            }
+        }
+    }
+
+    /**
+     * Get TYPE_NORMAL colors when wallpaper is visible, or fallback otherwise.
+     *
+     * @param which FLAG_LOCK or FLAG_SYSTEM
+     * @return colors
+     */
+    @Override
+    public GradientColors getColors(int which) {
+        return getColors(which, TYPE_NORMAL);
+    }
+
+    /**
+     * Wallpaper colors when the wallpaper is visible, fallback otherwise.
+     *
+     * @param which FLAG_LOCK or FLAG_SYSTEM
+     * @param type TYPE_NORMAL, TYPE_DARK or TYPE_EXTRA_DARK
+     * @return colors
+     */
+    @Override
+    public GradientColors getColors(int which, int type) {
+        return getColors(which, type, false /* ignoreVisibility */);
+    }
+
+    /**
+     * Get TYPE_NORMAL colors, possibly ignoring wallpaper visibility.
+     *
+     * @param which FLAG_LOCK or FLAG_SYSTEM
+     * @param ignoreWallpaperVisibility whether you want fallback colors or not if the wallpaper
+     *                                  isn't visible
+     * @return
+     */
+    public GradientColors getColors(int which, boolean ignoreWallpaperVisibility) {
+        return getColors(which, TYPE_NORMAL, ignoreWallpaperVisibility);
+    }
+
+    /**
+     *
+     * @param which FLAG_LOCK or FLAG_SYSTEM
+     * @param type TYPE_NORMAL, TYPE_DARK or TYPE_EXTRA_DARK
+     * @param ignoreWallpaperVisibility true if true wallpaper colors should be returning
+     *                                  if it's visible or not
+     * @return colors
+     */
+    public GradientColors getColors(int which, int type, boolean ignoreWallpaperVisibility) {
+        if (mWallpaperVisible || ignoreWallpaperVisibility) {
+            return super.getColors(which, type);
+        } else {
+            return mWpHiddenColors;
+        }
+    }
+
+    @VisibleForTesting
+    void setWallpaperVisible(boolean visible) {
+        if (mWallpaperVisible != visible) {
+            mWallpaperVisible = visible;
+            triggerColorsChanged(WallpaperManager.FLAG_SYSTEM);
+        }
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index e8dcf6c..80a6418 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -25,6 +25,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.HardwareUiLayout;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.volume.VolumeDialogImpl;
@@ -1227,7 +1228,7 @@
             mClickListener = clickListener;
             mLongClickListener = longClickListener;
             mGradientDrawable = new GradientDrawable(mContext);
-            mColorExtractor = Dependency.get(ColorExtractor.class);
+            mColorExtractor = Dependency.get(SysuiColorExtractor.class);
 
             // Window initialization
             Window window = getWindow();
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 0f69f47..a5ee198 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -427,11 +427,7 @@
         } else {
             actionsContainer.setVisibility(View.VISIBLE);
             if (mActionsGroup != null) {
-                // Hide extra views
-                for (int i = mActions.size(); i < mActionsGroup.getChildCount(); i++) {
-                    mActionsGroup.getChildAt(i).setVisibility(View.GONE);
-                }
-                // Add needed views
+                // Ensure we have as many buttons as actions
                 final LayoutInflater inflater = LayoutInflater.from(this);
                 while (mActionsGroup.getChildCount() < mActions.size()) {
                     final ImageView actionView = (ImageView) inflater.inflate(
@@ -439,6 +435,13 @@
                     mActionsGroup.addView(actionView);
                 }
 
+                // Update the visibility of all views
+                for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+                    mActionsGroup.getChildAt(i).setVisibility(i < mActions.size()
+                            ? View.VISIBLE
+                            : View.GONE);
+                }
+
                 // Recreate the layout
                 final boolean isLandscapePip = stackBounds != null &&
                         (stackBounds.width() > stackBounds.height());
@@ -460,10 +463,9 @@
                                 Log.w(TAG, "Failed to send action", e);
                             }
                         });
-                    } else {
-                        actionView.setAlpha(DISABLED_ACTION_ALPHA);
                     }
                     actionView.setEnabled(action.isEnabled());
+                    actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
 
                     // Update the margin between actions
                     LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java
index 40a63d7..b21cd95 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorInflater;
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -33,6 +34,7 @@
  * A view containing PIP controls including fullscreen, close, and media controls.
  */
 public class PipControlButtonView extends RelativeLayout {
+
     private OnFocusChangeListener mFocusChangeListener;
     private ImageView mIconImageView;
     ImageView mButtonImageView;
@@ -122,18 +124,37 @@
     }
 
     /**
+     * Sets the drawable for the button with the given drawable.
+     */
+    public void setImageDrawable(Drawable d) {
+        mIconImageView.setImageDrawable(d);
+    }
+
+    /**
      * Sets the drawable for the button with the given resource id.
      */
     public void setImageResource(int resId) {
-        mIconImageView.setImageResource(resId);
+        if (resId != 0) {
+            mIconImageView.setImageResource(resId);
+        }
+    }
+
+    /**
+     * Sets the text for description the with the given string.
+     */
+    public void setText(CharSequence text) {
+        mButtonImageView.setContentDescription(text);
+        mDescriptionTextView.setText(text);
     }
 
     /**
      * Sets the text for description the with the given resource id.
      */
     public void setText(int resId) {
-        mButtonImageView.setContentDescription(getContext().getString(resId));
-        mDescriptionTextView.setText(resId);
+        if (resId != 0) {
+            mButtonImageView.setContentDescription(getContext().getString(resId));
+            mDescriptionTextView.setText(resId);
+        }
     }
 
     private static void cancelAnimator(Animator animator) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
index acea3b6..10206d4 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
@@ -16,12 +16,20 @@
 
 package com.android.systemui.pip.tv;
 
+import android.app.ActivityManager;
+import android.app.PendingIntent.CanceledException;
+import android.app.RemoteAction;
 import android.content.Context;
+import android.graphics.Color;
 import android.media.session.MediaController;
 import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
 import android.view.View;
 import android.view.Gravity;
 import android.view.LayoutInflater;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.util.AttributeSet;
 
@@ -30,11 +38,19 @@
 import static android.media.session.PlaybackState.ACTION_PAUSE;
 import static android.media.session.PlaybackState.ACTION_PLAY;
 
+import java.util.ArrayList;
+import java.util.List;
+
 
 /**
  * A view containing PIP controls including fullscreen, close, and media controls.
  */
 public class PipControlsView extends LinearLayout {
+
+    private static final String TAG = PipControlsView.class.getSimpleName();
+
+    private static final float DISABLED_ACTION_ALPHA = 0.54f;
+
     /**
      * An interface to listen user action.
      */
@@ -47,19 +63,23 @@
 
     private MediaController mMediaController;
 
-    final PipManager mPipManager = PipManager.getInstance();
-    Listener mListener;
+    private final PipManager mPipManager = PipManager.getInstance();
+    private final LayoutInflater mLayoutInflater;
+    private final Handler mHandler;
+    private Listener mListener;
 
     private PipControlButtonView mFullButtonView;
     private PipControlButtonView mCloseButtonView;
     private PipControlButtonView mPlayPauseButtonView;
+    private ArrayList<PipControlButtonView> mCustomButtonViews = new ArrayList<>();
+    private List<RemoteAction> mCustomActions = new ArrayList<>();
 
     private PipControlButtonView mFocusedChild;
 
     private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
-            updatePlayPauseView();
+            updateUserActions();
         }
     };
 
@@ -95,9 +115,10 @@
 
     public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        LayoutInflater inflater = (LayoutInflater) getContext()
+        mLayoutInflater = (LayoutInflater) getContext()
                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        inflater.inflate(R.layout.tv_pip_controls, this);
+        mLayoutInflater.inflate(R.layout.tv_pip_controls, this);
+        mHandler = new Handler();
 
         setOrientation(LinearLayout.HORIZONTAL);
         setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
@@ -176,21 +197,74 @@
         if (mMediaController != null) {
             mMediaController.registerCallback(mMediaControllerCallback);
         }
-        updatePlayPauseView();
+        updateUserActions();
     }
 
-    private void updatePlayPauseView() {
-        int state = mPipManager.getPlaybackState();
-        if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) {
+    /**
+     * Updates the actions for the PIP. If there are no custom actions, then the media session
+     * actions are shown.
+     */
+    private void updateUserActions() {
+        if (!mCustomActions.isEmpty()) {
+            // Ensure we have as many buttons as actions
+            while (mCustomButtonViews.size() < mCustomActions.size()) {
+                PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate(
+                        R.layout.tv_pip_custom_control, this, false);
+                addView(buttonView);
+                mCustomButtonViews.add(buttonView);
+            }
+
+            // Update the visibility of all views
+            for (int i = 0; i < mCustomButtonViews.size(); i++) {
+                mCustomButtonViews.get(i).setVisibility(i < mCustomActions.size()
+                        ? View.VISIBLE
+                        : View.GONE);
+            }
+
+            // Update the state and visibility of the action buttons, and hide the rest
+            for (int i = 0; i < mCustomActions.size(); i++) {
+                final RemoteAction action = mCustomActions.get(i);
+                PipControlButtonView actionView = mCustomButtonViews.get(i);
+
+                // TODO: Check if the action drawable has changed before we reload it
+                action.getIcon().loadDrawableAsync(getContext(), d -> {
+                    d.setTint(Color.WHITE);
+                    actionView.setImageDrawable(d);
+                }, mHandler);
+                actionView.setText(action.getContentDescription());
+                if (action.isEnabled()) {
+                    actionView.setOnClickListener(v -> {
+                        try {
+                            action.getActionIntent().send();
+                        } catch (CanceledException e) {
+                            Log.w(TAG, "Failed to send action", e);
+                        }
+                    });
+                }
+                actionView.setEnabled(action.isEnabled());
+                actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
+            }
+
+            // Hide the media session buttons
             mPlayPauseButtonView.setVisibility(View.GONE);
         } else {
-            mPlayPauseButtonView.setVisibility(View.VISIBLE);
-            if (state == PipManager.PLAYBACK_STATE_PLAYING) {
-                mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white);
-                mPlayPauseButtonView.setText(R.string.pip_pause);
+            int state = mPipManager.getPlaybackState();
+            if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) {
+                mPlayPauseButtonView.setVisibility(View.GONE);
             } else {
-                mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white);
-                mPlayPauseButtonView.setText(R.string.pip_play);
+                mPlayPauseButtonView.setVisibility(View.VISIBLE);
+                if (state == PipManager.PLAYBACK_STATE_PLAYING) {
+                    mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white);
+                    mPlayPauseButtonView.setText(R.string.pip_pause);
+                } else {
+                    mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white);
+                    mPlayPauseButtonView.setText(R.string.pip_play);
+                }
+            }
+
+            // Hide all the custom action buttons
+            for (int i = 0; i < mCustomButtonViews.size(); i++) {
+                mCustomButtonViews.get(i).setVisibility(View.GONE);
             }
         }
     }
@@ -203,6 +277,9 @@
         mCloseButtonView.reset();
         mPlayPauseButtonView.reset();
         mFullButtonView.requestFocus();
+        for (int i = 0; i < mCustomButtonViews.size(); i++) {
+            mCustomButtonViews.get(i).reset();
+        }
     }
 
     /**
@@ -213,6 +290,15 @@
     }
 
     /**
+     * Updates the set of activity-defined actions.
+     */
+    public void setActions(List<RemoteAction> actions) {
+        mCustomActions.clear();
+        mCustomActions.addAll(actions);
+        updateUserActions();
+    }
+
+    /**
      * Returns the focused control button view to animate focused button.
      */
     PipControlButtonView getFocusedButton() {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index f98310d..ca58080 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -20,6 +20,7 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
 import android.app.IActivityManager;
+import android.app.RemoteAction;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -124,6 +125,7 @@
     private MediaController mPipMediaController;
     private String[] mLastPackagesResourceGranted;
     private PipNotification mPipNotification;
+    private ParceledListSlice mCustomActions;
 
     private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
 
@@ -187,7 +189,14 @@
         }
 
         @Override
-        public void onActionsChanged(ParceledListSlice actions) {}
+        public void onActionsChanged(ParceledListSlice actions) {
+            mCustomActions = actions;
+            mHandler.post(() -> {
+                for (int i = mListeners.size() - 1; i >= 0; --i) {
+                    mListeners.get(i).onPipMenuActionsChanged(mCustomActions);
+                }
+            });
+        }
     }
 
     private PipManager() { }
@@ -432,6 +441,7 @@
         }
         Intent intent = new Intent(mContext, PipMenuActivity.class);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(PipMenuActivity.EXTRA_CUSTOM_ACTIONS, mCustomActions);
         mContext.startActivity(intent);
     }
 
@@ -690,6 +700,8 @@
         void onPipActivityClosed();
         /** Invoked when the PIP menu gets shown. */
         void onShowPipMenu();
+        /** Invoked when the PIP menu actions change. */
+        void onPipMenuActionsChanged(ParceledListSlice actions);
         /** Invoked when the PIPed activity is about to return back to the fullscreen. */
         void onMoveToFullscreen();
         /** Invoked when we are above to start resizing the Pip. */
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
index ce1bea1..82018ce 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
@@ -19,22 +19,27 @@
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
 import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
 import android.os.Bundle;
 import android.view.View;
 
 import com.android.systemui.R;
 
+import java.util.Collections;
 /**
  * Activity to show the PIP menu to control PIP.
  */
 public class PipMenuActivity extends Activity implements PipManager.Listener {
     private static final String TAG = "PipMenuActivity";
 
+    static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
+
     private final PipManager mPipManager = PipManager.getInstance();
 
     private Animator mFadeInAnimation;
     private Animator mFadeOutAnimation;
-    private View mPipControlsView;
+    private PipControlsView mPipControlsView;
     private boolean mRestorePipSizeWhenClose;
 
     @Override
@@ -51,6 +56,15 @@
         mFadeOutAnimation = AnimatorInflater.loadAnimator(
                 this, R.anim.tv_pip_menu_fade_out_animation);
         mFadeOutAnimation.setTarget(mPipControlsView);
+
+        onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+
+        onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
     }
 
     private void restorePipAndFinish() {
@@ -96,6 +110,12 @@
     }
 
     @Override
+    public void onPipMenuActionsChanged(ParceledListSlice actions) {
+        boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
+        mPipControlsView.setActions(hasCustomActions ? actions.getList() : Collections.EMPTY_LIST);
+    }
+
+    @Override
     public void onShowPipMenu() { }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
index c8f4185..f0745a0 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
@@ -81,6 +82,11 @@
         }
 
         @Override
+        public void onPipMenuActionsChanged(ParceledListSlice actions) {
+            // no-op.
+        }
+
+        @Override
         public void onMoveToFullscreen() {
             dismissPipNotification();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
index eaf715f..5b3ec08 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
@@ -35,9 +35,7 @@
     public CellTileView(Context context) {
         super(context);
         mSignalDrawable = new SignalDrawable(mContext);
-        float dark = Utils.getColorAttr(context, android.R.attr.colorForeground) == 0xff000000
-                ? 1 : 0;
-        mSignalDrawable.setDarkIntensity(dark);
+        mSignalDrawable.setDarkIntensity(isDark(mContext));
         mSignalDrawable.setIntrinsicSize(context.getResources().getDimensionPixelSize(
                 R.dimen.qs_tile_icon_size));
     }
@@ -50,6 +48,10 @@
         }
     }
 
+    private static int isDark(Context context) {
+        return Utils.getColorAttr(context, android.R.attr.colorForeground) == 0xff000000 ? 1 : 0;
+    }
+
     public static class SignalIcon extends Icon {
 
         private final int mState;
@@ -64,7 +66,11 @@
 
         @Override
         public Drawable getDrawable(Context context) {
-            return null;
+            //TODO: Not the optimal solution to create this drawable
+            SignalDrawable d = new SignalDrawable(context);
+            d.setDarkIntensity(isDark(context));
+            d.setLevel(getState());
+            return d;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 319b463..d710244 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -46,6 +46,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
@@ -113,7 +114,7 @@
 
     private final float mScrimAlpha;
     private GradientDrawable mBackgroundScrim;
-    private final ColorExtractor mColorExtractor;
+    private final SysuiColorExtractor mColorExtractor;
     private Animator mBackgroundScrimAnimator;
 
     private RecentsTransitionHelper mTransitionHelper;
@@ -146,7 +147,7 @@
         mBackgroundScrim = new GradientDrawable(context);
         mBackgroundScrim.setCallback(this);
         mBackgroundScrim.setAlpha((int) (mScrimAlpha * 255));
-        mColorExtractor = Dependency.get(ColorExtractor.class);
+        mColorExtractor = Dependency.get(SysuiColorExtractor.class);
 
         LayoutInflater inflater = LayoutInflater.from(context);
         if (RecentsDebugFlags.Static.EnableStackActionButton) {
@@ -829,17 +830,23 @@
     }
 
     @Override
-    public void onColorsChanged(ColorExtractor extractor, int which) {
+    public void onColorsChanged(ColorExtractor colorExtractor, int which) {
         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
-            mBackgroundScrim.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM));
+            // Recents doesn't care about the wallpaper being visible or not, it always
+            // wants to scrim with wallpaper colors
+            mBackgroundScrim.setColors(
+                    mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, true));
         }
     }
 
     public void onStart() {
         mColorExtractor.addOnColorsChangedListener(this);
+        // Getting system scrim colors ignoring wallpaper visibility since it should never be grey.
+        ColorExtractor.GradientColors systemColors = mColorExtractor.getColors(
+                WallpaperManager.FLAG_SYSTEM, true);
         // We don't want to interpolate colors because we're defining the initial state.
         // Gradient should be set/ready when you open "Recents".
-        mBackgroundScrim.setColors(mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM), false);
+        mBackgroundScrim.setColors(systemColors, false);
     }
 
     public void onStop() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 169019f..25f3e25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -16,19 +16,15 @@
 
 package com.android.systemui.statusbar;
 
-import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.net.NetworkBadging;
 import android.telephony.SubscriptionInfo;
 import android.util.ArraySet;
 import android.util.AttributeSet;
@@ -80,10 +76,8 @@
     private boolean mEthernetVisible = false;
     private int mEthernetIconId = 0;
     private int mLastEthernetIconId = -1;
-    private int mWifiBadgeId = -1;
     private boolean mWifiVisible = false;
     private int mWifiStrengthId = 0;
-    private int mLastWifiBadgeId = -1;
     private int mLastWifiStrengthId = -1;
     private boolean mWifiIn;
     private boolean mWifiOut;
@@ -291,7 +285,6 @@
             boolean activityIn, boolean activityOut, String description, boolean isTransient) {
         mWifiVisible = statusIcon.visible && !mBlockWifi;
         mWifiStrengthId = statusIcon.icon;
-        mWifiBadgeId = statusIcon.iconOverlay;
         mWifiDescription = statusIcon.contentDescription;
         mWifiIn = activityIn && mActivityEnabled && mWifiVisible;
         mWifiOut = activityOut && mActivityEnabled && mWifiVisible;
@@ -428,7 +421,6 @@
             mWifi.setImageDrawable(null);
             mWifiDark.setImageDrawable(null);
             mLastWifiStrengthId = -1;
-            mLastWifiBadgeId = -1;
         }
 
         for (PhoneState state : mPhoneStates) {
@@ -484,16 +476,10 @@
                     (mEthernetVisible ? "VISIBLE" : "GONE")));
 
         if (mWifiVisible) {
-            if (mWifiStrengthId != mLastWifiStrengthId || mWifiBadgeId != mLastWifiBadgeId) {
-                if (mWifiBadgeId == -1) {
-                    setIconForView(mWifi, mWifiStrengthId);
-                    setIconForView(mWifiDark, mWifiStrengthId);
-                } else {
-                    setBadgedWifiIconForView(mWifi, mWifiStrengthId, mWifiBadgeId);
-                    setBadgedWifiIconForView(mWifiDark, mWifiStrengthId, mWifiBadgeId);
-                }
+            if (mWifiStrengthId != mLastWifiStrengthId) {
+                setIconForView(mWifi, mWifiStrengthId);
+                setIconForView(mWifiDark, mWifiStrengthId);
                 mLastWifiStrengthId = mWifiStrengthId;
-                mLastWifiBadgeId = mWifiBadgeId;
             }
             mWifiGroup.setContentDescription(mWifiDescription);
             mWifiGroup.setVisibility(View.VISIBLE);
@@ -558,10 +544,6 @@
         // Using the imageView's context to retrieve the Drawable so that theme is preserved.
         Drawable icon = imageView.getContext().getDrawable(iconId);
 
-        setScaledIcon(imageView, icon);
-    }
-
-    private void setScaledIcon(ImageView imageView, Drawable icon) {
         if (mIconScaleFactor == 1.f) {
             imageView.setImageDrawable(icon);
         } else {
@@ -569,33 +551,6 @@
         }
     }
 
-    /**
-     * Creates and sets a LayerDrawable from the given ids on the given view.
-     *
-     * <p>This method will also scale the icon by {@link #mIconScaleFactor} if appropriate.
-     */
-    private void setBadgedWifiIconForView(ImageView imageView, @DrawableRes int wifiPieId,
-            @DrawableRes int badgeId) {
-        // TODO(sghuman): Delete this method and revert to N badging logic
-        // Using the imageView's context to retrieve the Drawable so that theme is preserved.;
-        LayerDrawable icon = new LayerDrawable(new Drawable[] {
-                imageView.getContext().getDrawable(wifiPieId),
-                imageView.getContext().getDrawable(NetworkBadging.BADGING_NONE)});
-
-        // The LayerDrawable shares an underlying state so we must mutate the object to change the
-        // color between the light and dark themes.
-        icon.mutate().setTint(getColorAttr(imageView.getContext(), R.attr.singleToneColor));
-
-        setScaledIcon(imageView, icon);
-    }
-
-    /** Returns the given color attribute value, or white if not defined. */
-    @ColorInt private static int getColorAttr(Context context, int attr) {
-        TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
-        @ColorInt int colorAccent = ta.getColor(0, Color.WHITE);
-        ta.recycle();
-        return colorAccent;
-    }
 
     @Override
     public void onDarkChanged(Rect tintArea, float darkIntensity, int tint) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index bd59fb0..82e6a35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -106,6 +106,7 @@
                     mPanel.setPanelScrimMinFraction((float) expandedHeight
                             / mPanel.getMaxPanelHeight());
                     mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight);
+                    mPanel.startExpandingFromPeek();
                     // This call needs to be after the expansion start otherwise we will get a
                     // flicker of one frame as it's not expanded yet.
                     mHeadsUpManager.unpinAll();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index b970b1b..95c2fc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -407,6 +407,10 @@
         return Math.abs(yDiff) >= Math.abs(xDiff);
     }
 
+    protected void startExpandingFromPeek() {
+        mStatusBar.handlePeekToExpandTransistion();
+    }
+
     protected void startExpandMotion(float newX, float newY, boolean startTracking,
             float expandedHeight) {
         mInitialOffsetOnTouch = expandedHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index f502bb5..bc278e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -36,6 +36,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.ScrimView;
@@ -78,7 +79,7 @@
     private final View mHeadsUpScrim;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
-    private final ColorExtractor mColorExtractor;
+    private final SysuiColorExtractor mColorExtractor;
     private ColorExtractor.GradientColors mLockColors;
     private ColorExtractor.GradientColors mLockColorsDark;
     private ColorExtractor.GradientColors mSystemColors;
@@ -131,15 +132,17 @@
         mLightBarController = lightBarController;
         mScrimBehindAlpha = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
 
-        mColorExtractor = Dependency.get(ColorExtractor.class);
+        mColorExtractor = Dependency.get(SysuiColorExtractor.class);
         mColorExtractor.addOnColorsChangedListener(this);
-        mLockColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK);
-        mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM);
+        mLockColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
+                ColorExtractor.TYPE_NORMAL, true /* ignoreVisibility */);
+        mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
+                ColorExtractor.TYPE_NORMAL, true /* ignoreVisibility */);
         // Darker gradient for the top scrim (mScrimInFront)
         mLockColorsDark = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
-                ColorExtractor.TYPE_DARK);
+                ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
         mSystemColorsDark = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
-                ColorExtractor.TYPE_DARK);
+                ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
         mNeedsDrawableColorUpdate = true;
 
         updateHeadsUpScrim(false);
@@ -659,18 +662,18 @@
     @Override
     public void onColorsChanged(ColorExtractor colorExtractor, int which) {
         if ((which & WallpaperManager.FLAG_LOCK) != 0) {
-            mLockColors = colorExtractor.getColors(WallpaperManager.FLAG_LOCK,
-                    ColorExtractor.TYPE_NORMAL);
-            mLockColorsDark = colorExtractor.getColors(WallpaperManager.FLAG_LOCK,
-                    ColorExtractor.TYPE_DARK);
+            mLockColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
+                    ColorExtractor.TYPE_NORMAL, true /* ignoreVisibility */);
+            mLockColorsDark = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
+                    ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
             mNeedsDrawableColorUpdate = true;
             scheduleUpdate();
         }
         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
-            mSystemColors = colorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
-                    ColorExtractor.TYPE_NORMAL);
-            mSystemColorsDark = colorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
-                    ColorExtractor.TYPE_DARK);
+            mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
+                    ColorExtractor.TYPE_NORMAL, mKeyguardShowing);
+            mSystemColorsDark = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
+                    ColorExtractor.TYPE_DARK, mKeyguardShowing);
             mNeedsDrawableColorUpdate = true;
             scheduleUpdate();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
index 5ebcde7..083da51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
@@ -81,21 +81,21 @@
             {-1.9f / VIEWPORT, -1.9f / VIEWPORT},
     };
 
-    // Rounded corners are achieved by arcing a circle of radius `mCornerRadius` from its tangent
-    // points along the curve. On the top and left corners of the triangle, the tangents are as
-    // follows:
+    // Rounded corners are achieved by arcing a circle of radius `R` from its tangent points along
+    // the curve (curve ≡ triangle). On the top and left corners of the triangle, the tangents are
+    // as follows:
     //      1) Along the straight lines (y = 0 and x = width):
-    //          Ps = R / tan(45)
+    //          Ps = circleOffset + R
     //      2) Along the diagonal line (y = x):
     //          Pd = √((Ps^2) / 2)
-    //
-    // I.e., the points where we stop the straight lines lie at (starting drawing from the bottom-
-    // right corner and counter-clockwise): (width, height - Radius), (width, Ps), (width - Pd, Pd),
-    // (Pd, height - Pd), (Ps, height), and finally (width - Radius, height).
-    private static final double TAN_THETA = Math.tan(Math.PI / 8.f);
-    private final float mCornerRadius;
-    private final float mCircleOffsetStraight;
-    private final float mCircleOffsetDiag;
+    //              or (remember: sin(π/4) ≈ 0.7071)
+    //          Pd = (circleOffset + R - 0.7071, height - R - 0.7071)
+    //         Where Pd is the (x,y) coords of the point that intersects the circle at the bottom
+    //         left of the triangle
+    private static final float RADIUS_RATIO = 0.75f / 17f;
+    private static final float DIAG_OFFSET_MULTIPLIER = 0.707107f;
+    // How far the circle defining the corners is inset from the edges
+    private final float mAppliedCornerInset;
 
     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Paint mForegroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -129,10 +129,8 @@
         mHandler = new Handler();
         setDarkIntensity(0);
 
-        mCornerRadius = context.getResources()
-                .getDimensionPixelSize(R.dimen.stat_sys_mobile_signal_corner_radius);
-        mCircleOffsetStraight = mCornerRadius / (float) TAN_THETA;
-        mCircleOffsetDiag = (float) Math.sqrt(Math.pow(mCircleOffsetStraight, 2.f) / 2.f);
+        mAppliedCornerInset = context.getResources()
+                .getDimensionPixelSize(R.dimen.stat_sys_mobile_signal_circle_inset);
     }
 
     public void setIntrinsicSize(int size) {
@@ -226,38 +224,42 @@
         }
         mFullPath.reset();
         mFullPath.setFillType(FillType.WINDING);
-        float width = getBounds().width();
-        float height = getBounds().height();
-        float padding = (int) (PAD * width);  // Stay on pixel boundary
+        final float width = getBounds().width();
+        final float height = getBounds().height();
+        final float padding = (int) (PAD * width);  // Stay on pixel boundary
+        final float cornerRadius = RADIUS_RATIO * height;
+        // Offset from circle where the hypotenuse meets the circle
+        final float diagOffset = DIAG_OFFSET_MULTIPLIER * cornerRadius;
 
         // 1 - Bottom right, above corner
-        mFullPath.moveTo(width - padding, height - padding - mCornerRadius);
+        mFullPath.moveTo(width - padding, height - padding - cornerRadius);
         // 2 - Line to top right, below corner
-        mFullPath.lineTo(width - padding, padding + mCircleOffsetStraight);
+        mFullPath.lineTo(width - padding, padding + cornerRadius + mAppliedCornerInset);
         // 3 - Arc to top right, on hypotenuse
         mFullPath.arcTo(
-                width - padding - (2 * mCornerRadius),
-                padding + mCircleOffsetStraight - mCornerRadius,
+                width - padding - (2 * cornerRadius),
+                padding + mAppliedCornerInset,
                 width - padding,
-                padding + mCircleOffsetStraight + mCornerRadius,
+                padding + mAppliedCornerInset + (2 * cornerRadius),
                 0.f, -135.f, false
         );
         // 4 - Line to bottom left, on hypotenuse
-        mFullPath.lineTo(padding + mCircleOffsetDiag, height - padding - mCircleOffsetDiag);
+        mFullPath.lineTo(padding + mAppliedCornerInset + cornerRadius - diagOffset,
+                height - padding - cornerRadius - diagOffset);
         // 5 - Arc to bottom left, on leg
         mFullPath.arcTo(
-                padding + mCircleOffsetStraight - mCornerRadius,
-                height - padding - (2 * mCornerRadius),
-                padding + mCircleOffsetStraight + mCornerRadius,
+                padding + mAppliedCornerInset,
+                height - padding - (2 * cornerRadius),
+                padding + mAppliedCornerInset + ( 2 * cornerRadius),
                 height - padding,
                 -135.f, -135.f, false
         );
         // 6 - Line to bottom rght, before corner
-        mFullPath.lineTo(width - padding - mCornerRadius, height - padding);
+        mFullPath.lineTo(width - padding - cornerRadius, height - padding);
         // 7 - Arc to beginning (bottom right, above corner)
         mFullPath.arcTo(
-                width - padding - (2 * mCornerRadius),
-                height - padding - (2 * mCornerRadius),
+                width - padding - (2 * cornerRadius),
+                height - padding - (2 * cornerRadius),
                 width - padding,
                 height - padding,
                 90.f, -90.f, false
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 908de1c..193077f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -168,6 +168,7 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.fragments.ExtensionFragmentListener;
@@ -660,7 +661,7 @@
 
     // Tracks notifications currently visible in mNotificationStackScroller and
     // emits visibility events via NoMan on changes.
-    private final Runnable mVisibilityReporter = new Runnable() {
+    protected final Runnable mVisibilityReporter = new Runnable() {
         private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications =
                 new ArraySet<>();
         private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications =
@@ -733,7 +734,7 @@
     private HashMap<String, Entry> mPendingNotifications = new HashMap<>();
     private boolean mClearAllEnabled;
     @Nullable private View mAmbientIndicationContainer;
-    private ColorExtractor mColorExtractor;
+    private SysuiColorExtractor mColorExtractor;
     private ForegroundServiceController mForegroundServiceController;
 
     private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
@@ -780,7 +781,7 @@
         mOverlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
 
-        mColorExtractor = Dependency.get(ColorExtractor.class);
+        mColorExtractor = Dependency.get(SysuiColorExtractor.class);
         mColorExtractor.addOnColorsChangedListener(this);
 
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
@@ -3821,6 +3822,17 @@
         }
     }
 
+    void handlePeekToExpandTransistion() {
+        try {
+            // consider the transition from peek to expanded to be a panel open,
+            // but not one that clears notification effects.
+            int notificationLoad = mNotificationData.getActiveNotifications().size();
+            mBarService.onPanelRevealed(false, notificationLoad);
+        } catch (RemoteException ex) {
+            // Won't fail unless the world has ended.
+        }
+    }
+
     /**
      * The LEDs are turned off when the notification panel is shown, even just a little bit.
      * See also StatusBar.setPanelExpanded for another place where we attempt to do this.
@@ -3836,8 +3848,6 @@
                 int notificationLoad = mNotificationData.getActiveNotifications().size();
                 if (pinnedHeadsUp && isPanelFullyCollapsed())  {
                     notificationLoad = 1;
-                } else {
-                    mMetricsLogger.histogram("note_load", notificationLoad);
                 }
                 mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
             } else {
@@ -4515,10 +4525,10 @@
         // Ignore visibility since we calculate the theme based on the real colors,
         // not the current state.
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
-            useDarkTheme = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK)
+            useDarkTheme = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK, true /* vis */)
                     .supportsDarkText();
         } else {
-            useDarkTheme = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM)
+            useDarkTheme = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, true /* vis */)
                     .supportsDarkText();
         }
 
@@ -5198,6 +5208,12 @@
         mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD
                 || mFingerprintUnlockController.getMode()
                         == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+        // When in wake-and-unlock we may not have received a change to mState
+        // but we still should not be dozing, manually set to false.
+        if (mFingerprintUnlockController.getMode() ==
+                FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
+            mDozing = false;
+        }
         mStatusBarWindowManager.setDozing(mDozing);
         mStatusBarKeyguardViewManager.setDozing(mDozing);
         updateDozingState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index c02ce0e..2771011 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -67,29 +67,15 @@
 
     public static class IconState {
         public final boolean visible;
-
         public final int icon;
-
-        /**
-         * Optional iconOverlay resource id.
-         *
-         * <p>Set to -1 if not present.
-         */
-        public final int iconOverlay;
-
         public final String contentDescription;
 
-        public IconState(boolean visible, int icon, int iconOverlay, String contentDescription) {
+        public IconState(boolean visible, int icon, String contentDescription) {
             this.visible = visible;
             this.icon = icon;
-            this.iconOverlay = iconOverlay;
             this.contentDescription = contentDescription;
         }
 
-        public IconState(boolean visible, int icon, String contentDescription) {
-            this(visible, icon, -1 /* iconOverlay */, contentDescription);
-        }
-
         public IconState(boolean visible, int icon, int contentDescription,
                 Context context) {
             this(visible, icon, context.getString(contentDescription));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index c21f444..39f7d12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -24,7 +24,6 @@
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
-import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -92,7 +91,6 @@
     private final DataSaverController mDataSaverController;
     private final CurrentUserTracker mUserTracker;
     private Config mConfig;
-    private final NetworkScoreManager mNetworkScoreManager;
 
     // Subcontrollers.
     @VisibleForTesting
@@ -149,12 +147,9 @@
     public NetworkControllerImpl(Context context, Looper bgLooper,
             DeviceProvisionedController deviceProvisionedController) {
         this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
-                context.getSystemService(NetworkScoreManager.class),
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
                 (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
-                SubscriptionManager.from(context),
-                Config.readConfig(context),
-                bgLooper,
+                SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
                 new CallbackHandler(),
                 new AccessPointControllerImpl(context, bgLooper),
                 new DataUsageController(context),
@@ -165,12 +160,8 @@
 
     @VisibleForTesting
     NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
-            NetworkScoreManager networkScoreManager,
-            TelephonyManager telephonyManager,
-            WifiManager wifiManager,
-            SubscriptionManager subManager,
-            Config config,
-            Looper bgLooper,
+            TelephonyManager telephonyManager, WifiManager wifiManager,
+            SubscriptionManager subManager, Config config, Looper bgLooper,
             CallbackHandler callbackHandler,
             AccessPointControllerImpl accessPointController,
             DataUsageController dataUsageController,
@@ -193,7 +184,6 @@
 
         // wifi
         mWifiManager = wifiManager;
-        mNetworkScoreManager = networkScoreManager;
 
         mLocale = mContext.getResources().getConfiguration().locale;
         mAccessPoints = accessPointController;
@@ -207,7 +197,7 @@
             }
         });
         mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
-                mCallbackHandler, this, mNetworkScoreManager);
+                mCallbackHandler, this);
 
         mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 2104cb1..2819624 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -17,50 +17,33 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.database.ContentObserver;
-import android.net.NetworkBadging;
 import android.net.NetworkCapabilities;
-import android.net.NetworkKey;
-import android.net.NetworkScoreManager;
-import android.net.ScoredNetwork;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiNetworkScoreCache;
-import android.net.wifi.WifiNetworkScoreCache.CacheListener;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
-import android.provider.Settings;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.AsyncChannel;
-import com.android.settingslib.Utils;
 import com.android.settingslib.wifi.WifiStatusTracker;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
-import com.android.systemui.R;
-
 import java.util.Objects;
-import java.util.List;
 
 
 public class WifiSignalController extends
         SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
-
     private final WifiManager mWifiManager;
     private final AsyncChannel mWifiChannel;
     private final boolean mHasMobileData;
-    private final NetworkScoreManager mNetworkScoreManager;
-    private final WifiNetworkScoreCache mScoreCache;
     private final WifiStatusTracker mWifiTracker;
 
-    private boolean mScoringUiEnabled = false;
-
     public WifiSignalController(Context context, boolean hasMobileData,
-            CallbackHandler callbackHandler, NetworkControllerImpl networkController,
-            NetworkScoreManager networkScoreManager) {
+            CallbackHandler callbackHandler, NetworkControllerImpl networkController) {
         super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
                 callbackHandler, networkController);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
@@ -85,44 +68,6 @@
                 AccessibilityContentDescriptions.WIFI_NO_CONNECTION
                 );
 
-        mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(handler) {
-            @Override
-            public void networkCacheUpdated(List<ScoredNetwork> networks) {
-                mCurrentState.badgeEnum = getWifiBadgeEnum();
-                notifyListenersIfNecessary();
-            }
-        });
-
-        // Setup scoring
-        mNetworkScoreManager = networkScoreManager;
-        configureScoringGating();
-        registerScoreCache();
-    }
-
-    private void configureScoringGating() {
-        ContentObserver observer = new ContentObserver(new Handler(Looper.getMainLooper())) {
-            @Override
-            public void onChange(boolean selfChange) {
-                mScoringUiEnabled =
-                        Settings.Global.getInt(
-                                mContext.getContentResolver(),
-                                Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
-            }
-        };
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.NETWORK_SCORING_UI_ENABLED),
-                false /* notifyForDescendants */,
-                observer);
-
-        observer.onChange(false /* selfChange */); // Set the initial values
-    }
-
-    private void registerScoreCache() {
-        Log.d(mTag, "Registered score cache");
-        mNetworkScoreManager.registerNetworkScoreCache(
-                NetworkKey.TYPE_WIFI,
-                mScoreCache,
-                NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
     }
 
     @Override
@@ -143,77 +88,27 @@
                     ("," + mContext.getString(R.string.accessibility_quick_settings_no_internet));
         }
 
-        IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(),
-                Utils.getWifiBadgeResource(mCurrentState.badgeEnum), contentDescription);
-        IconState qsIcon = new IconState(
-                mCurrentState.connected, getQsCurrentIconId(),
-                Utils.getWifiBadgeResource(mCurrentState.badgeEnum), contentDescription);
+        IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription);
+        IconState qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconId(),
+                contentDescription);
         callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon,
                 ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut,
                 wifiDesc, mCurrentState.isTransient);
     }
 
-    @Override
-    public int getCurrentIconId() {
-        if (mCurrentState.badgeEnum != NetworkBadging.BADGING_NONE) {
-            return Utils.WIFI_PIE_FOR_BADGING[mCurrentState.level];
-        }
-        return super.getCurrentIconId();
-    }
-
     /**
      * Extract wifi state directly from broadcasts about changes in wifi state.
      */
     public void handleBroadcast(Intent intent) {
-        // Update the WifiStatusTracker with the new information and update the score cache.
-        NetworkKey previousNetworkKey = mWifiTracker.networkKey;
         mWifiTracker.handleBroadcast(intent);
-        updateScoreCacheIfNecessary(previousNetworkKey);
-
-        mCurrentState.isTransient = mWifiTracker.state == WifiManager.WIFI_STATE_ENABLING
-                || mWifiTracker.state == WifiManager.WIFI_AP_STATE_DISABLING
-                || mWifiTracker.connecting;
         mCurrentState.enabled = mWifiTracker.enabled;
         mCurrentState.connected = mWifiTracker.connected;
         mCurrentState.ssid = mWifiTracker.ssid;
         mCurrentState.rssi = mWifiTracker.rssi;
         mCurrentState.level = mWifiTracker.level;
-        mCurrentState.badgeEnum = getWifiBadgeEnum();
         notifyListenersIfNecessary();
     }
 
-    /**
-     * Clears old scores out of the cache and requests new scores if the network key has changed.
-     *
-     * <p>New scores are requested asynchronously.
-     */
-    private void updateScoreCacheIfNecessary(NetworkKey previousNetworkKey) {
-        if (mWifiTracker.networkKey == null) {
-            return;
-        }
-        if ((previousNetworkKey == null) || !mWifiTracker.networkKey.equals(previousNetworkKey)) {
-            mScoreCache.clearScores();
-            mNetworkScoreManager.requestScores(new NetworkKey[]{mWifiTracker.networkKey});
-        }
-    }
-
-    /**
-     * Returns the wifi badge enum for the current {@link #mWifiTracker} state.
-     *
-     * <p>{@link #updateScoreCacheIfNecessary} should be called prior to this method.
-     */
-    private int getWifiBadgeEnum() {
-        if (!mScoringUiEnabled || mWifiTracker.networkKey == null) {
-            return NetworkBadging.BADGING_NONE;
-        }
-        ScoredNetwork score = mScoreCache.getScoredNetwork(mWifiTracker.networkKey);
-
-        if (score != null) {
-            return score.calculateBadge(mWifiTracker.rssi);
-        }
-        return NetworkBadging.BADGING_NONE;
-    }
-
     @VisibleForTesting
     void setActivity(int wifiActivity) {
         mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
@@ -254,7 +149,6 @@
 
     static class WifiState extends SignalController.State {
         String ssid;
-        int badgeEnum;
         boolean isTransient;
 
         @Override
@@ -262,7 +156,6 @@
             super.copyFrom(s);
             WifiState state = (WifiState) s;
             ssid = state.ssid;
-            badgeEnum = state.badgeEnum;
             isTransient = state.isTransient;
         }
 
@@ -270,7 +163,6 @@
         protected void toString(StringBuilder builder) {
             super.toString(builder);
             builder.append(',').append("ssid=").append(ssid);
-            builder.append(',').append("badgeEnum=").append(badgeEnum);
             builder.append(',').append("isTransient=").append(isTransient);
         }
 
@@ -278,7 +170,6 @@
         public boolean equals(Object o) {
             return super.equals(o)
                     && Objects.equals(((WifiState) o).ssid, ssid)
-                    && (((WifiState) o).badgeEnum == badgeEnum)
                     && (((WifiState) o).isTransient == isTransient);
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
new file mode 100644
index 0000000..1ed5f56
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
@@ -0,0 +1,102 @@
+/*
+ * 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.colorextraction;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.WallpaperColors;
+import android.app.WallpaperManager;
+import android.graphics.Color;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
+
+import com.android.systemui.SysuiTestCase;
+
+import com.google.android.colorextraction.ColorExtractor;
+import com.google.android.colorextraction.types.Tonal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests color extraction generation.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SysuiColorExtractorTests extends SysuiTestCase {
+
+    private static int[] sWhich = new int[]{
+            WallpaperManager.FLAG_SYSTEM,
+            WallpaperManager.FLAG_LOCK};
+    private static int[] sTypes = new int[]{
+            ColorExtractor.TYPE_NORMAL,
+            ColorExtractor.TYPE_DARK,
+            ColorExtractor.TYPE_EXTRA_DARK};
+
+    @Test
+    public void getColors_usesGreyIfWallpaperNotVisible() {
+        ColorExtractor.GradientColors fallbackColors = new ColorExtractor.GradientColors();
+        fallbackColors.setMainColor(ColorExtractor.FALLBACK_COLOR);
+        fallbackColors.setSecondaryColor(ColorExtractor.FALLBACK_COLOR);
+
+        SysuiColorExtractor extractor = new SysuiColorExtractor(getContext(), new Tonal(), false);
+        simulateEvent(extractor);
+        extractor.setWallpaperVisible(false);
+
+        for (int which : sWhich) {
+            for (int type : sTypes) {
+                assertEquals("Not using fallback!", extractor.getColors(which, type),
+                        fallbackColors);
+            }
+        }
+    }
+
+    @Test
+    public void getColors_doesntUseFallbackIfVisible() {
+        ColorExtractor.GradientColors colors = new ColorExtractor.GradientColors();
+        colors.setMainColor(Color.RED);
+        colors.setSecondaryColor(Color.RED);
+
+        SysuiColorExtractor extractor = new SysuiColorExtractor(getContext(),
+                (inWallpaperColors, outGradientColorsNormal, outGradientColorsDark,
+                        outGradientColorsExtraDark) -> {
+                    outGradientColorsNormal.set(colors);
+                    outGradientColorsDark.set(colors);
+                    outGradientColorsExtraDark.set(colors);
+                    return true;
+                }, false);
+        simulateEvent(extractor);
+        extractor.setWallpaperVisible(true);
+
+        for (int which : sWhich) {
+            for (int type : sTypes) {
+                assertEquals("Not using extracted colors!",
+                        extractor.getColors(which, type), colors);
+            }
+        }
+    }
+
+    private void simulateEvent(SysuiColorExtractor extractor) {
+        // Let's fake a color event
+        extractor.onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), null, null, 0),
+                WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK);
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index db6647c..0e3ea7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -20,37 +20,54 @@
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.fail;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
 
 import android.app.Notification;
 import android.metrics.LogMaker;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IPowerManager;
-import android.os.Looper;
+import android.os.Message;
 import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.support.test.filters.SmallTest;
 import android.support.test.metricshelper.MetricsAsserts;
 import android.support.test.runner.AndroidJUnit4;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.MessageHandler;
+import android.testing.TestableLooper.RunWithLooper;
 import android.util.DisplayMetrics;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.keyguard.KeyguardHostView.OnDismissAction;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.statusbar.ActivatableNotificationView;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
@@ -58,21 +75,26 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
 public class StatusBarTest extends SysuiTestCase {
 
     StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     UnlockMethodCache mUnlockMethodCache;
     KeyguardIndicationController mKeyguardIndicationController;
     NotificationStackScrollLayout mStackScroller;
-    StatusBar mStatusBar;
+    TestableStatusBar mStatusBar;
     FakeMetricsLogger mMetricsLogger;
     HeadsUpManager mHeadsUpManager;
     NotificationData mNotificationData;
     PowerManager mPowerManager;
     SystemServicesProxy mSystemServicesProxy;
     NotificationPanelView mNotificationPanelView;
+    IStatusBarService mBarService;
+    ArrayList<Entry> mNotificationList;
     private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
 
     @Before
@@ -86,18 +108,20 @@
         mNotificationData = mock(NotificationData.class);
         mSystemServicesProxy = mock(SystemServicesProxy.class);
         mNotificationPanelView = mock(NotificationPanelView.class);
+        mNotificationList = mock(ArrayList.class);
         IPowerManager powerManagerService = mock(IPowerManager.class);
         HandlerThread handlerThread = new HandlerThread("TestThread");
         handlerThread.start();
         mPowerManager = new PowerManager(mContext, powerManagerService,
                 new Handler(handlerThread.getLooper()));
         when(powerManagerService.isInteractive()).thenReturn(true);
+        mBarService = mock(IStatusBarService.class);
 
         mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
         mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
                 mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
-                mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView);
-
+                mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView,
+                mBarService);
         doAnswer(invocation -> {
             OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
             onDismissAction.onDismiss();
@@ -111,6 +135,15 @@
         }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
 
         when(mStackScroller.getActivatedChild()).thenReturn(null);
+        TestableLooper.get(this).setMessageHandler(new MessageHandler() {
+            @Override
+            public boolean onMessageHandled(Message m) {
+                if (m.getCallback() == mStatusBar.mVisibilityReporter) {
+                    return false;
+                }
+                return true;
+            }
+        });
     }
 
     @Test
@@ -284,11 +317,80 @@
         assertFalse(mStatusBar.shouldPeek(entry, sbn));
     }
 
+    @Test
+    public void testLogHidden() {
+        try {
+            mStatusBar.handleVisibleToUserChanged(false);
+            verify(mBarService, times(1)).onPanelHidden();
+            verify(mBarService, never()).onPanelRevealed(anyBoolean(), anyInt());
+        } catch (RemoteException e) {
+            fail();
+        }
+    }
+
+    @Test
+    public void testPanelOpenForPeek() {
+        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
+        when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList);
+        when(mNotificationList.size()).thenReturn(5);
+        when(mNotificationPanelView.isFullyCollapsed()).thenReturn(true);
+        mStatusBar.setBarStateForTest(StatusBarState.SHADE);
+
+        try {
+            mStatusBar.handleVisibleToUserChanged(true);
+
+            verify(mBarService, never()).onPanelHidden();
+            verify(mBarService, times(1)).onPanelRevealed(false, 1);
+        } catch (RemoteException e) {
+            fail();
+        }
+        TestableLooper.get(this).processAllMessages();
+    }
+
+    @Test
+    public void testPanelOpenAndClear() {
+        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
+        when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList);
+        when(mNotificationList.size()).thenReturn(5);
+        when(mNotificationPanelView.isFullyCollapsed()).thenReturn(false);
+        mStatusBar.setBarStateForTest(StatusBarState.SHADE);
+
+        try {
+            mStatusBar.handleVisibleToUserChanged(true);
+
+            verify(mBarService, never()).onPanelHidden();
+            verify(mBarService, times(1)).onPanelRevealed(true, 5);
+        } catch (RemoteException e) {
+            fail();
+        }
+        TestableLooper.get(this).processAllMessages();
+    }
+
+    @Test
+    public void testPanelOpenAndNoClear() {
+        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
+        when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList);
+        when(mNotificationList.size()).thenReturn(5);
+        when(mNotificationPanelView.isFullyCollapsed()).thenReturn(false);
+        mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+
+        try {
+            mStatusBar.handleVisibleToUserChanged(true);
+
+            verify(mBarService, never()).onPanelHidden();
+            verify(mBarService, times(1)).onPanelRevealed(false, 5);
+        } catch (RemoteException e) {
+            fail();
+        }
+        TestableLooper.get(this).processAllMessages();
+    }
+
     static class TestableStatusBar extends StatusBar {
         public TestableStatusBar(StatusBarKeyguardViewManager man,
                 UnlockMethodCache unlock, KeyguardIndicationController key,
                 NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd,
-                PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView) {
+                PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView,
+                IStatusBarService barService) {
             mStatusBarKeyguardViewManager = man;
             mUnlockMethodCache = unlock;
             mKeyguardIndicationController = key;
@@ -299,11 +401,11 @@
             mPowerManager = pm;
             mSystemServicesProxy = ssp;
             mNotificationPanel = panelView;
+            mBarService = barService;
         }
 
-        @Override
-        protected H createHandler() {
-            return null;
+        public void setBarStateForTest(int state) {
+            mState = state;
         }
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
index cb20639..51bd7bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
@@ -18,6 +18,7 @@
 import android.os.HandlerThread;
 import android.support.test.runner.AndroidJUnit4;
 import android.telephony.SubscriptionInfo;
+import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 505e1d8..a8319a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -19,7 +19,6 @@
 import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
-import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
 import android.os.Looper;
 import android.telephony.PhoneStateListener;
@@ -84,7 +83,6 @@
     protected Config mConfig;
     protected CallbackHandler mCallbackHandler;
     protected SubscriptionDefaults mMockSubDefaults;
-    protected NetworkScoreManager mMockNetworkScoreManager;
     protected DeviceProvisionedController mMockProvisionController;
     protected DeviceProvisionedListener mUserCallback;
 
@@ -113,8 +111,6 @@
         mMockCm = mock(ConnectivityManager.class);
         mMockSubDefaults = mock(SubscriptionDefaults.class);
         mNetCapabilities = new NetworkCapabilities();
-        mMockNetworkScoreManager = mock(NetworkScoreManager.class);
-
         when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true);
         when(mMockCm.getDefaultNetworkCapabilitiesForUser(0)).thenReturn(
                 new NetworkCapabilities[] { mNetCapabilities });
@@ -135,8 +131,7 @@
             return null;
         }).when(mMockProvisionController).addCallback(any());
 
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager,
-                mMockTm, mMockWm, mMockSm,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
                 mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mMockProvisionController);
@@ -177,8 +172,8 @@
     protected NetworkControllerImpl setUpNoMobileData() {
       when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
       NetworkControllerImpl networkControllerNoMobile
-              = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager, mMockTm,
-                        mMockWm, mMockSm, mConfig, mContext.getMainLooper(), mCallbackHandler,
+              = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
+                        mConfig, mContext.getMainLooper(), mCallbackHandler,
                         mock(AccessPointControllerImpl.class),
                         mock(DataUsageController.class), mMockSubDefaults,
                         mock(DeviceProvisionedController.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index dfe00f9..8d106b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -91,8 +91,7 @@
     public void test4gDataIcon() {
         // Switch to showing 4g icon and re-initialize the NetworkController.
         mConfig.show4gForLte = true;
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager,
-                mMockTm, mMockWm, mMockSm,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
                 mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
                 mock(DataUsageController.class), mMockSubDefaults,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 1627925..be3802b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -54,8 +54,7 @@
         // Turn off mobile network support.
         Mockito.when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
         // Create a new NetworkController as this is currently handled in constructor.
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager,
-                mMockTm, mMockWm, mMockSm,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
                 mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mock(DeviceProvisionedController.class));
@@ -117,8 +116,7 @@
         // Turn off mobile network support.
         Mockito.when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
         // Create a new NetworkController as this is currently handled in constructor.
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager,
-                mMockTm, mMockWm, mMockSm,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
                 mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mock(DeviceProvisionedController.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index dbaa2c5..ffd0165 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -1,47 +1,24 @@
 package com.android.systemui.statusbar.policy;
 
 import android.content.Intent;
-import android.net.NetworkBadging;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkKey;
-import android.net.RssiCurve;
-import android.net.ScoredNetwork;
-import android.net.WifiKey;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiNetworkScoreCache;
-import android.os.Bundle;
-import android.provider.Settings;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.settingslib.Utils;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.Matchers;
 import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
 import static junit.framework.Assert.assertEquals;
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -50,13 +27,6 @@
     private static final int MIN_RSSI = -100;
     private static final int MAX_RSSI = -55;
 
-    private static final int LATCH_TIMEOUT = 2000;
-    private static final String TEST_SSID = "\"Test SSID\"";
-    private static final String TEST_BSSID = "00:00:00:00:00:00";
-
-    private final List<NetworkKey> mRequestedKeys = new ArrayList<>();
-    private CountDownLatch mRequestScoresLatch;
-
     @Test
     public void testWifiIcon() {
         String testSsid = "Test SSID";
@@ -77,79 +47,6 @@
     }
 
     @Test
-    public void testBadgedWifiIcon() throws Exception {
-        // TODO(sghuman): Refactor this setup code when creating a test for the badged QsIcon.
-        int testLevel = 1;
-        RssiCurve mockBadgeCurve = mock(RssiCurve.class);
-        Bundle attr = new Bundle();
-        attr.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve);
-        ScoredNetwork score =
-                new ScoredNetwork(
-                        new NetworkKey(new WifiKey(TEST_SSID, TEST_BSSID)),
-                        null,
-                        false /* meteredHint */,
-                        attr);
-
-        // Must set the Settings value before instantiating the NetworkControllerImpl due to bugs in
-        // TestableSettingsProvider.
-        Settings.Global.putString(mContext.getContentResolver(),
-                Settings.Global.NETWORK_SCORING_UI_ENABLED,
-                "1");
-        super.setUp(); // re-instantiate NetworkControllImpl now that setting has been updated
-        setupNetworkScoreManager();
-
-        // Test Requesting Scores
-        mRequestScoresLatch = new CountDownLatch(1);
-        setWifiEnabled(true);
-        setWifiState(true, TEST_SSID, TEST_BSSID);
-        mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
-
-        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) NetworkBadging.BADGING_SD);
-
-        ArgumentCaptor<WifiNetworkScoreCache> scoreCacheCaptor =
-                ArgumentCaptor.forClass(WifiNetworkScoreCache.class);
-        verify(mMockNetworkScoreManager).registerNetworkScoreCache(
-                anyInt(),
-                scoreCacheCaptor.capture(),
-                Matchers.anyInt());
-        scoreCacheCaptor.getValue().updateScores(Arrays.asList(score));
-
-        //  Test badge is set
-        setWifiLevel(testLevel);
-
-        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
-        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
-                anyBoolean(), iconArg.capture(), any(), anyBoolean(), anyBoolean(),
-                any(), anyBoolean());
-        IconState iconState = iconArg.getValue();
-
-        assertEquals("Badged Wifi Resource is set",
-                Utils.WIFI_PIE_FOR_BADGING[testLevel],
-                iconState.icon);
-        assertEquals("SD Badge is set",
-                Utils.getWifiBadgeResource(NetworkBadging.BADGING_SD),
-                iconState.iconOverlay);
-    }
-
-    private void setupNetworkScoreManager() {
-        // Capture requested keys and count down latch if present
-        doAnswer(
-                new Answer<Boolean>() {
-                    @Override
-                    public Boolean answer(InvocationOnMock input) {
-                        if (mRequestScoresLatch != null) {
-                            mRequestScoresLatch.countDown();
-                        }
-                        NetworkKey[] keys = (NetworkKey[]) input.getArguments()[0];
-                        for (NetworkKey key : keys) {
-                            mRequestedKeys.add(key);
-                        }
-                        return true;
-                    }
-                }).when(mMockNetworkScoreManager).requestScores(Matchers.<NetworkKey[]>any());
-    }
-
-    @Test
     public void testQsWifiIcon() {
         String testSsid = "Test SSID";
 
@@ -200,7 +97,7 @@
     @Test
     public void testRoamingIconDuringWifi() {
         // Setup normal connection
-        String testSsid = "\"Test SSID\"";
+        String testSsid = "Test SSID";
         int testLevel = 2;
         setWifiEnabled(true);
         setWifiState(true, testSsid);
@@ -241,19 +138,12 @@
     }
 
     protected void setWifiState(boolean connected, String ssid) {
-        setWifiState(connected, ssid, null);
-    }
-
-    protected void setWifiState(boolean connected, String ssid, String bssid) {
         Intent i = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         NetworkInfo networkInfo = Mockito.mock(NetworkInfo.class);
         Mockito.when(networkInfo.isConnected()).thenReturn(connected);
 
         WifiInfo wifiInfo = Mockito.mock(WifiInfo.class);
         Mockito.when(wifiInfo.getSSID()).thenReturn(ssid);
-        if (bssid != null) {
-            Mockito.when(wifiInfo.getBSSID()).thenReturn(bssid);
-        }
 
         i.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
         i.putExtra(WifiManager.EXTRA_WIFI_INFO, wifiInfo);
@@ -278,7 +168,7 @@
 
         Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
                 enabledArg.capture(), any(), iconArg.capture(), anyBoolean(),
-                anyBoolean(),  descArg.capture(), anyBoolean());
+                anyBoolean(), descArg.capture(), anyBoolean());
         IconState iconState = iconArg.getValue();
         assertEquals("WiFi enabled, in quick settings", enabled, (boolean) enabledArg.getValue());
         assertEquals("WiFi connected, in quick settings", connected, iconState.visible);
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 8cecb4b..93fae58 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -219,6 +219,10 @@
     // Package: com.android.systemui
     NOTE_TV_PIP = 1100;
 
+    // Notify the user that open Wi-Fi networks are available.
+    // Package: android
+    NOTE_NETWORK_AVAILABLE = 17303299;
+
     // Communicate to the user about remote bugreports.
     // Package: android
     NOTE_REMOTE_BUGREPORT = 678432343;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 9e2f52a..a58ba09 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -243,6 +243,8 @@
 
     private WindowsForAccessibilityCallback mWindowsForAccessibilityCallback;
 
+    private boolean mIsAccessibilityButtonShown;
+
     private UserState getCurrentUserStateLocked() {
         return getUserStateLocked(mCurrentUserId);
     }
@@ -872,21 +874,21 @@
     }
 
     /**
-     * Invoked remotely over AIDL by SysUi when the availability of the accessibility
+     * Invoked remotely over AIDL by SysUi when the visibility of the accessibility
      * button within the system's navigation area has changed.
      *
-     * @param available {@code true} if the accessibility button is available to the
+     * @param shown {@code true} if the accessibility button is shown to the
      *                  user, {@code false} otherwise
      */
     @Override
-    public void notifyAccessibilityButtonAvailabilityChanged(boolean available) {
+    public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Caller does not hold permission "
                     + android.Manifest.permission.STATUS_BAR_SERVICE);
         }
         synchronized (mLock) {
-            notifyAccessibilityButtonAvailabilityChangedLocked(available);
+            notifyAccessibilityButtonVisibilityChangedLocked(shown);
         }
     }
 
@@ -1191,13 +1193,14 @@
         }
     }
 
-    private void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
+    private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) {
         final UserState state = getCurrentUserStateLocked();
-        state.mIsAccessibilityButtonAvailable = available;
+        mIsAccessibilityButtonShown = available;
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
             final Service service = state.mBoundServices.get(i);
             if (service.mRequestAccessibilityButton) {
-                service.notifyAccessibilityButtonAvailabilityChangedLocked(available);
+                service.notifyAccessibilityButtonAvailabilityChangedLocked(
+                        service.isAccessibilityButtonAvailableLocked(state));
             }
         }
     }
@@ -1724,7 +1727,7 @@
         scheduleUpdateInputFilter(userState);
         scheduleUpdateClientsIfNeededLocked(userState);
         updateRelevantEventsLocked(userState);
-        updateAccessibilityButtonTargets(userState);
+        updateAccessibilityButtonTargetsLocked(userState);
     }
 
     private void updateAccessibilityFocusBehaviorLocked(UserState userState) {
@@ -2174,18 +2177,12 @@
         }
     }
 
-    private void updateAccessibilityButtonTargets(UserState userState) {
-        final List<Service> services;
-        synchronized (mLock) {
-            services = userState.mBoundServices;
-            int numServices = services.size();
-            for (int i = 0; i < numServices; i++) {
-                final Service service = services.get(i);
-                if (service.mRequestAccessibilityButton) {
-                    boolean available = service.mComponentName.equals(
-                            userState.mServiceAssignedToAccessibilityButton);
-                    service.notifyAccessibilityButtonAvailabilityChangedLocked(available);
-                }
+    private void updateAccessibilityButtonTargetsLocked(UserState userState) {
+        for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
+            final Service service = userState.mBoundServices.get(i);
+            if (service.mRequestAccessibilityButton) {
+                service.notifyAccessibilityButtonAvailabilityChangedLocked(
+                        service.isAccessibilityButtonAvailableLocked(userState));
             }
         }
     }
@@ -2492,7 +2489,7 @@
 
                 case MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER: {
                     showAccessibilityButtonTargetSelection();
-                }
+                } break;
             }
         }
 
@@ -2647,6 +2644,10 @@
 
         boolean mRequestAccessibilityButton;
 
+        boolean mReceivedAccessibilityButtonCallbackSinceBind;
+
+        boolean mLastAccessibilityButtonCallbackState;
+
         int mFetchFlags;
 
         long mNotificationTimeout;
@@ -3587,9 +3588,8 @@
                     return false;
                 }
                 userState = getCurrentUserStateLocked();
+                return isAccessibilityButtonAvailableLocked(userState);
             }
-
-            return mRequestAccessibilityButton && userState.mIsAccessibilityButtonAvailable;
         }
 
         @Override
@@ -3647,6 +3647,7 @@
                 mService = null;
             }
             mServiceInterface = null;
+            mReceivedAccessibilityButtonCallbackSinceBind = false;
         }
 
         public boolean isConnectedLocked() {
@@ -3719,6 +3720,48 @@
             }
         }
 
+        private boolean isAccessibilityButtonAvailableLocked(UserState userState) {
+            // If the service does not request the accessibility button, it isn't available
+            if (!mRequestAccessibilityButton) {
+                return false;
+            }
+
+            // If the accessibility button isn't currently shown, it cannot be available to services
+            if (!mIsAccessibilityButtonShown) {
+                return false;
+            }
+
+            // If magnification is on and assigned to the accessibility button, services cannot be
+            if (userState.mIsNavBarMagnificationEnabled
+                    && userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+                return false;
+            }
+
+            int requestingServices = 0;
+            for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
+                final Service service = userState.mBoundServices.get(i);
+                if (service.mRequestAccessibilityButton) {
+                    requestingServices++;
+                }
+            }
+
+            if (requestingServices == 1) {
+                // If only a single service is requesting, it must be this service, and the
+                // accessibility button is available to it
+                return true;
+            } else {
+                // With more than one active service, we derive the target from the user's settings
+                if (userState.mServiceAssignedToAccessibilityButton == null) {
+                    // If the user has not made an assignment, we treat the button as available to
+                    // all services until the user interacts with the button to make an assignment
+                    return true;
+                } else {
+                    // If an assignment was made, it defines availability
+                    return mComponentName.equals(userState.mServiceAssignedToAccessibilityButton);
+                }
+            }
+        }
+
         /**
          * Notifies an accessibility service client for a scheduled event given the event type.
          *
@@ -3866,6 +3909,13 @@
         }
 
         private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) {
+            // Only notify the service if it's not been notified or the state has changed
+            if (mReceivedAccessibilityButtonCallbackSinceBind
+                    && (mLastAccessibilityButtonCallbackState == available)) {
+                return;
+            }
+            mReceivedAccessibilityButtonCallbackSinceBind = true;
+            mLastAccessibilityButtonCallbackState = available;
             final IAccessibilityServiceClient listener;
             synchronized (mLock) {
                 listener = mServiceInterface;
@@ -4865,7 +4915,6 @@
 
         public int mSoftKeyboardShowMode = 0;
 
-        public boolean mIsAccessibilityButtonAvailable;
         public boolean mIsNavBarMagnificationAssignedToAccessibilityButton;
         public ComponentName mServiceAssignedToAccessibilityButton;
 
@@ -4945,9 +4994,6 @@
             mIsNavBarMagnificationAssignedToAccessibilityButton = false;
             mIsAutoclickEnabled = false;
             mSoftKeyboardShowMode = 0;
-
-            // Clear state tracked from system UI
-            mIsAccessibilityButtonAvailable = false;
         }
 
         public void destroyUiAutomationService() {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 073d7b2..3ae0511 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -255,7 +255,9 @@
 
             final ViewNode node = nodes[i];
             if (node == null) {
-                Slog.w(TAG, "fillStructureWithAllowedValues(): no node for " + viewState.id);
+                if (sVerbose) {
+                    Slog.v(TAG, "fillStructureWithAllowedValues(): no node for " + viewState.id);
+                }
                 continue;
             }
 
@@ -862,11 +864,9 @@
         final int numContexts = mContexts.size();
         for (int i = 0; i < numContexts; i++) {
             final FillContext context = mContexts.get(i);
-            // TODO: create a function that gets just one node so it doesn't create an array
-            // unnecessarily
-            final ViewNode[] nodes = context.findViewNodesByAutofillIds(id);
-            if (nodes != null) {
-                AutofillValue candidate = nodes[0].getAutofillValue();
+            final ViewNode node = context.findViewNodeByAutofillId(id);
+            if (node != null) {
+                final AutofillValue candidate = node.getAutofillValue();
                 if (sDebug) {
                     Slog.d(TAG, "getValueFromContexts(" + id + ") at " + i + ": " + candidate);
                 }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 73f1705..4810f4f 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -47,6 +47,7 @@
 import android.os.IDeviceIdleController;
 import android.os.IInterface;
 import android.os.Parcel;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -345,7 +346,7 @@
     }
 
     private static boolean isCallerSystem() {
-        return getCallingUserId() == UserHandle.USER_SYSTEM;
+        return Binder.getCallingUid() == Process.SYSTEM_UID;
     }
 
     private ServiceConnection createServiceConnection(
diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
index 7080c41..516f8f6 100644
--- a/services/core/java/com/android/server/MasterClearReceiver.java
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import android.app.PendingIntent;
 import android.app.ProgressDialog;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -23,6 +24,7 @@
 import android.os.AsyncTask;
 import android.os.RecoverySystem;
 import android.os.storage.StorageManager;
+import android.provider.Settings;
 import android.telephony.euicc.EuiccManager;
 import android.util.Log;
 import android.util.Slog;
@@ -31,14 +33,29 @@
 import com.android.internal.R;
 
 import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 public class MasterClearReceiver extends BroadcastReceiver {
     private static final String TAG = "MasterClear";
+    private static final String ACTION_WIPE_EUICC_DATA =
+            "com.android.internal.action.wipe_euicc_data";
+    private static final long DEFAULT_EUICC_WIPING_TIMEOUT_MILLIS = 30000L; // 30 s
     private boolean mWipeExternalStorage;
-    private boolean mWipeEims;
+    private boolean mWipeEsims;
+    private static CountDownLatch mEuiccFactoryResetLatch;
 
     @Override
     public void onReceive(final Context context, final Intent intent) {
+        if (ACTION_WIPE_EUICC_DATA.equals(intent.getAction())) {
+            if (getResultCode() != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
+                int detailedCode = intent.getIntExtra(
+                    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0);
+                Slog.e(TAG, "Error wiping euicc data, Detailed code = " + detailedCode);
+            }
+            mEuiccFactoryResetLatch.countDown();
+            return;
+        }
         if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) {
             if (!"google.com".equals(intent.getStringExtra("from"))) {
                 Slog.w(TAG, "Ignoring master clear request -- not from trusted server.");
@@ -57,7 +74,7 @@
         final boolean shutdown = intent.getBooleanExtra("shutdown", false);
         final String reason = intent.getStringExtra(Intent.EXTRA_REASON);
         mWipeExternalStorage = intent.getBooleanExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false);
-        mWipeEims = intent.getBooleanExtra(Intent.EXTRA_WIPE_ESIMS, false);
+        mWipeEsims = intent.getBooleanExtra(Intent.EXTRA_WIPE_ESIMS, false);
         final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false)
                 || intent.getBooleanExtra(Intent.EXTRA_FORCE_FACTORY_RESET, false);
 
@@ -77,7 +94,7 @@
             }
         };
 
-        if (mWipeExternalStorage || mWipeEims) {
+        if (mWipeExternalStorage || mWipeEsims) {
             // thr will be started at the end of this task.
             new WipeDataTask(context, thr).execute();
         } else {
@@ -112,10 +129,31 @@
                         Context.STORAGE_SERVICE);
                 sm.wipeAdoptableDisks();
             }
-            if (mWipeEims) {
+            if (mWipeEsims) {
                 EuiccManager euiccManager = (EuiccManager) mContext.getSystemService(
                         Context.EUICC_SERVICE);
-                // STOPSHIP: add EuiccManager API to factory reset eUICC
+                Intent intent = new Intent(mContext, MasterClearReceiver.class);
+                intent.setAction(ACTION_WIPE_EUICC_DATA);
+                PendingIntent callbackIntent = PendingIntent.getBroadcast(
+                        mContext,
+                        0 /* requestCode */,
+                        intent,
+                        PendingIntent.FLAG_UPDATE_CURRENT);
+                mEuiccFactoryResetLatch = new CountDownLatch(1);
+                euiccManager.eraseSubscriptions(callbackIntent);
+                try {
+                    long waitingTime = Settings.Global.getLong(
+                            mContext.getContentResolver(),
+                            Settings.Global.EUICC_WIPING_TIMEOUT_MILLIS,
+                            DEFAULT_EUICC_WIPING_TIMEOUT_MILLIS);
+
+                    if (!mEuiccFactoryResetLatch.await(waitingTime, TimeUnit.MILLISECONDS)) {
+                        Slog.e(TAG, "Timeout wiping eUICC data.");
+                    }
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    Slog.e(TAG, "Wiping eUICC data interrupted", e);
+                }
             }
             return null;
         }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c417484..756e274 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -81,6 +81,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.EventLog;
+import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
@@ -175,6 +176,9 @@
         long mStartVisibleTime;
         long mEndTime;
         int mNumActive;
+
+        // Temp output of foregroundAppShownEnoughLocked
+        long mHideTime;
     }
 
     /**
@@ -622,19 +626,19 @@
                             != ActivityManager.APP_START_MODE_NORMAL) {
                         if (stopping == null) {
                             stopping = new ArrayList<>();
-                            String compName = service.name.flattenToShortString();
-                            EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);
-                            StringBuilder sb = new StringBuilder(64);
-                            sb.append("Stopping service due to app idle: ");
-                            UserHandle.formatUid(sb, service.appInfo.uid);
-                            sb.append(" ");
-                            TimeUtils.formatDuration(service.createTime
-                                    - SystemClock.elapsedRealtime(), sb);
-                            sb.append(" ");
-                            sb.append(compName);
-                            Slog.w(TAG, sb.toString());
-                            stopping.add(service);
                         }
+                        String compName = service.name.flattenToShortString();
+                        EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);
+                        StringBuilder sb = new StringBuilder(64);
+                        sb.append("Stopping service due to app idle: ");
+                        UserHandle.formatUid(sb, service.appInfo.uid);
+                        sb.append(" ");
+                        TimeUtils.formatDuration(service.createTime
+                                - SystemClock.elapsedRealtime(), sb);
+                        sb.append(" ");
+                        sb.append(compName);
+                        Slog.w(TAG, sb.toString());
+                        stopping.add(service);
                     }
                 }
             }
@@ -736,50 +740,90 @@
         }
     }
 
+    boolean foregroundAppShownEnoughLocked(ActiveForegroundApp aa, long nowElapsed) {
+        if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Shown enough: pkg=" + aa.mPackageName + ", uid="
+                + aa.mUid);
+        boolean canRemove = false;
+        aa.mHideTime = Long.MAX_VALUE;
+        if (aa.mShownWhileTop) {
+            // If the app was ever at the top of the screen while the foreground
+            // service was running, then we can always just immediately remove it.
+            canRemove = true;
+            if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - shown while on top");
+        } else if (mScreenOn || aa.mShownWhileScreenOn) {
+            final long minTime = aa.mStartVisibleTime
+                    + (aa.mStartTime != aa.mStartVisibleTime
+                            ? mAm.mConstants.FGSERVICE_SCREEN_ON_AFTER_TIME
+                            : mAm.mConstants.FGSERVICE_MIN_SHOWN_TIME);
+            if (nowElapsed >= minTime) {
+                // If shown while the screen is on, and it has been shown for
+                // at least the minimum show time, then we can now remove it.
+                if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - shown long enough with screen on");
+                canRemove = true;
+            } else {
+                // This is when we will be okay to stop telling the user.
+                long reportTime = nowElapsed + mAm.mConstants.FGSERVICE_MIN_REPORT_TIME;
+                aa.mHideTime = reportTime > minTime ? reportTime : minTime;
+                if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "NO -- wait " + (aa.mHideTime-nowElapsed)
+                        + " with screen on");
+            }
+        } else {
+            final long minTime = aa.mEndTime
+                    + mAm.mConstants.FGSERVICE_SCREEN_ON_BEFORE_TIME;
+            if (nowElapsed >= minTime) {
+                // If the foreground service has only run while the screen is
+                // off, but it has been gone now for long enough that we won't
+                // care to tell the user about it when the screen comes back on,
+                // then we can remove it now.
+                if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - gone long enough with screen off");
+                canRemove = true;
+            } else {
+                // This is when we won't care about this old fg service.
+                aa.mHideTime = minTime;
+                if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "NO -- wait " + (aa.mHideTime-nowElapsed)
+                        + " with screen off");
+            }
+        }
+        return canRemove;
+    }
+
     void updateForegroundApps(ServiceMap smap) {
         // This is called from the handler without the lock held.
         ArrayList<ActiveForegroundApp> active = null;
         synchronized (mAm) {
             final long now = SystemClock.elapsedRealtime();
-            final long nowPlusMin = now + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME;
             long nextUpdateTime = Long.MAX_VALUE;
             if (smap != null) {
+                if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Updating foreground apps for user "
+                        + smap.mUserId);
                 for (int i = smap.mActiveForegroundApps.size()-1; i >= 0; i--) {
                     ActiveForegroundApp aa = smap.mActiveForegroundApps.valueAt(i);
-                    if (aa.mEndTime != 0 && (mScreenOn || aa.mShownWhileScreenOn)) {
-                        if (!aa.mShownWhileTop && aa.mEndTime < (aa.mStartVisibleTime
-                                + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME)) {
-                            // Check to see if this should still be displayed...  we continue
-                            // until it has been shown for at least the timeout duration.
-                            if (nowPlusMin >= aa.mStartVisibleTime) {
-                                // All over!
-                                smap.mActiveForegroundApps.removeAt(i);
-                                smap.mActiveForegroundAppsChanged = true;
-                                continue;
-                            } else {
-                                long hideTime = aa.mStartVisibleTime
-                                        + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME;
-                                if (hideTime < nextUpdateTime) {
-                                    nextUpdateTime = hideTime;
-                                }
-                            }
-                        } else {
+                    if (aa.mEndTime != 0) {
+                        boolean canRemove = foregroundAppShownEnoughLocked(aa, now);
+                        if (canRemove) {
                             // This was up for longer than the timeout, so just remove immediately.
                             smap.mActiveForegroundApps.removeAt(i);
                             smap.mActiveForegroundAppsChanged = true;
                             continue;
                         }
+                        if (aa.mHideTime < nextUpdateTime) {
+                            nextUpdateTime = aa.mHideTime;
+                        }
                     }
                     if (!aa.mAppOnTop) {
                         if (active == null) {
                             active = new ArrayList<>();
                         }
+                        if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Adding active: pkg="
+                                + aa.mPackageName + ", uid=" + aa.mUid);
                         active.add(aa);
                     }
                 }
                 smap.removeMessages(ServiceMap.MSG_UPDATE_FOREGROUND_APPS);
                 if (nextUpdateTime < Long.MAX_VALUE) {
-                    Message msg = smap.obtainMessage();
+                    if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Next update time in: "
+                            + (nextUpdateTime-now));
+                    Message msg = smap.obtainMessage(ServiceMap.MSG_UPDATE_FOREGROUND_APPS);
                     smap.sendMessageAtTime(msg, nextUpdateTime
                             + SystemClock.uptimeMillis() - SystemClock.elapsedRealtime());
                 }
@@ -882,15 +926,14 @@
             active.mNumActive--;
             if (active.mNumActive <= 0) {
                 active.mEndTime = SystemClock.elapsedRealtime();
-                if (active.mShownWhileTop || active.mEndTime >= (active.mStartVisibleTime
-                        + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME)) {
+                if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Ended running of service");
+                if (foregroundAppShownEnoughLocked(active, active.mEndTime)) {
                     // Have been active for long enough that we will remove it immediately.
                     smap.mActiveForegroundApps.remove(r.packageName);
                     smap.mActiveForegroundAppsChanged = true;
                     requestUpdateActiveForegroundAppsLocked(smap, 0);
-                } else {
-                    requestUpdateActiveForegroundAppsLocked(smap, active.mStartVisibleTime
-                            + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME);
+                } else if (active.mHideTime < Long.MAX_VALUE){
+                    requestUpdateActiveForegroundAppsLocked(smap, active.mHideTime);
                 }
             }
         }
@@ -904,26 +947,44 @@
             // services that were started while the screen was off.
             if (screenOn) {
                 final long nowElapsed = SystemClock.elapsedRealtime();
+                if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Screen turned on");
                 for (int i = mServiceMap.size()-1; i >= 0; i--) {
                     ServiceMap smap = mServiceMap.valueAt(i);
+                    long nextUpdateTime = Long.MAX_VALUE;
                     boolean changed = false;
                     for (int j = smap.mActiveForegroundApps.size()-1; j >= 0; j--) {
                         ActiveForegroundApp active = smap.mActiveForegroundApps.valueAt(j);
-                        if (!active.mShownWhileScreenOn) {
-                            changed = true;
-                            active.mShownWhileScreenOn = mScreenOn;
-                            active.mStartVisibleTime = nowElapsed;
-                            if (active.mEndTime != 0) {
-                                active.mEndTime = nowElapsed;
+                        if (active.mEndTime == 0) {
+                            if (!active.mShownWhileScreenOn) {
+                                active.mShownWhileScreenOn = true;
+                                active.mStartVisibleTime = nowElapsed;
+                            }
+                        } else {
+                            if (!active.mShownWhileScreenOn
+                                    && active.mStartVisibleTime == active.mStartTime) {
+                                // If this was never shown while the screen was on, then we will
+                                // count the time it started being visible as now, to tell the user
+                                // about it now that they have a screen to look at.
+                                active.mEndTime = active.mStartVisibleTime = nowElapsed;
+                            }
+                            if (foregroundAppShownEnoughLocked(active, nowElapsed)) {
+                                // Have been active for long enough that we will remove it
+                                // immediately.
+                                smap.mActiveForegroundApps.remove(active.mPackageName);
+                                smap.mActiveForegroundAppsChanged = true;
+                                changed = true;
+                            } else {
+                                if (active.mHideTime < nextUpdateTime) {
+                                    nextUpdateTime = active.mHideTime;
+                                }
                             }
                         }
                     }
                     if (changed) {
-                        requestUpdateActiveForegroundAppsLocked(smap,
-                                nowElapsed + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME);
-                    } else if (smap.mActiveForegroundApps.size() > 0) {
-                        // Just being paranoid.
+                        // Need to immediately update.
                         requestUpdateActiveForegroundAppsLocked(smap, 0);
+                    } else if (nextUpdateTime < Long.MAX_VALUE) {
+                        requestUpdateActiveForegroundAppsLocked(smap, nextUpdateTime);
                     }
                 }
             }
@@ -2318,7 +2379,7 @@
             return true;
         }
 
-        // Is someone still bound to us keepign us running?
+        // Is someone still bound to us keeping us running?
         if (!knowConn) {
             hasConn = r.hasAutoCreateConnections();
         }
@@ -3741,6 +3802,17 @@
                             pw.println();
                         }
                     }
+                    if (smap.hasMessagesOrCallbacks()) {
+                        if (needSep) {
+                            pw.println();
+                        }
+                        printedAnything = true;
+                        needSep = true;
+                        pw.print("  Handler - user ");
+                        pw.print(user);
+                        pw.println(":");
+                        smap.dumpMine(new PrintWriterPrinter(pw), "    ");
+                    }
                 }
             }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 5749f31..6c3fe91 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -35,8 +35,14 @@
     // Key names stored in the settings value.
     private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
     private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time";
-    private static final String KEY_FOREGROUND_SERVICE_UI_MIN_TIME
-            = "foreground_service_ui_min_time";
+    private static final String KEY_FGSERVICE_MIN_SHOWN_TIME
+            = "fgservice_min_shown_time";
+    private static final String KEY_FGSERVICE_MIN_REPORT_TIME
+            = "fgservice_min_report_time";
+    private static final String KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME
+            = "fgservice_screen_on_before_time";
+    private static final String KEY_FGSERVICE_SCREEN_ON_AFTER_TIME
+            = "fgservice_screen_on_after_time";
     private static final String KEY_CONTENT_PROVIDER_RETAIN_TIME = "content_provider_retain_time";
     private static final String KEY_GC_TIMEOUT = "gc_timeout";
     private static final String KEY_GC_MIN_INTERVAL = "gc_min_interval";
@@ -58,7 +64,10 @@
 
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
     private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
-    private static final long DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME = 30*1000;
+    private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
+    private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
+    private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000;
+    private static final long DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME = 5*1000;
     private static final long DEFAULT_CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
     private static final long DEFAULT_GC_TIMEOUT = 5*1000;
     private static final long DEFAULT_GC_MIN_INTERVAL = 60*1000;
@@ -85,8 +94,26 @@
     // before we start restricting what it can do.
     public long BACKGROUND_SETTLE_TIME = DEFAULT_BACKGROUND_SETTLE_TIME;
 
-    // The minimum time a foreground service will be shown as running in the notification UI.
-    public long FOREGROUND_SERVICE_UI_MIN_TIME = DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME;
+    // The minimum time we allow a foreground service to run with a notification and the
+    // screen on without otherwise telling the user about it.  (If it runs for less than this,
+    // it will still be reported to the user as a running app for at least this amount of time.)
+    public long FGSERVICE_MIN_SHOWN_TIME = DEFAULT_FGSERVICE_MIN_SHOWN_TIME;
+
+    // If a foreground service is shown for less than FGSERVICE_MIN_SHOWN_TIME, we will display
+    // the background app running notification about it for at least this amount of time (if it
+    // is larger than the remaining shown time).
+    public long FGSERVICE_MIN_REPORT_TIME = DEFAULT_FGSERVICE_MIN_REPORT_TIME;
+
+    // The minimum amount of time the foreground service needs to have remain being shown
+    // before the screen goes on for us to consider it not worth showing to the user.  That is
+    // if an app has a foreground service that stops itself this amount of time or more before
+    // the user turns on the screen, we will just let it go without the user being told about it.
+    public long FGSERVICE_SCREEN_ON_BEFORE_TIME = DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME;
+
+    // The minimum amount of time a foreground service should remain reported to the user if
+    // it is stopped when the screen turns on.  This is the time from when the screen turns
+    // on until we will stop reporting it.
+    public long FGSERVICE_SCREEN_ON_AFTER_TIME = DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME;
 
     // How long we will retain processes hosting content providers in the "last activity"
     // state before allowing them to drop down to the regular cached LRU list.  This is
@@ -225,8 +252,14 @@
                     DEFAULT_MAX_CACHED_PROCESSES);
             BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME,
                     DEFAULT_BACKGROUND_SETTLE_TIME);
-            FOREGROUND_SERVICE_UI_MIN_TIME = mParser.getLong(KEY_FOREGROUND_SERVICE_UI_MIN_TIME,
-                    DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME);
+            FGSERVICE_MIN_SHOWN_TIME = mParser.getLong(KEY_FGSERVICE_MIN_SHOWN_TIME,
+                    DEFAULT_FGSERVICE_MIN_SHOWN_TIME);
+            FGSERVICE_MIN_REPORT_TIME = mParser.getLong(KEY_FGSERVICE_MIN_REPORT_TIME,
+                    DEFAULT_FGSERVICE_MIN_REPORT_TIME);
+            FGSERVICE_SCREEN_ON_BEFORE_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME,
+                    DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME);
+            FGSERVICE_SCREEN_ON_AFTER_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME,
+                    DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME);
             CONTENT_PROVIDER_RETAIN_TIME = mParser.getLong(KEY_CONTENT_PROVIDER_RETAIN_TIME,
                     DEFAULT_CONTENT_PROVIDER_RETAIN_TIME);
             GC_TIMEOUT = mParser.getLong(KEY_GC_TIMEOUT,
@@ -284,8 +317,14 @@
         pw.println(MAX_CACHED_PROCESSES);
         pw.print("  "); pw.print(KEY_BACKGROUND_SETTLE_TIME); pw.print("=");
         pw.println(BACKGROUND_SETTLE_TIME);
-        pw.print("  "); pw.print(KEY_FOREGROUND_SERVICE_UI_MIN_TIME); pw.print("=");
-        pw.println(FOREGROUND_SERVICE_UI_MIN_TIME);
+        pw.print("  "); pw.print(KEY_FGSERVICE_MIN_SHOWN_TIME); pw.print("=");
+        pw.println(FGSERVICE_MIN_SHOWN_TIME);
+        pw.print("  "); pw.print(KEY_FGSERVICE_MIN_REPORT_TIME); pw.print("=");
+        pw.println(FGSERVICE_MIN_REPORT_TIME);
+        pw.print("  "); pw.print(KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME); pw.print("=");
+        pw.println(FGSERVICE_SCREEN_ON_BEFORE_TIME);
+        pw.print("  "); pw.print(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME); pw.print("=");
+        pw.println(FGSERVICE_SCREEN_ON_AFTER_TIME);
         pw.print("  "); pw.print(KEY_CONTENT_PROVIDER_RETAIN_TIME); pw.print("=");
         pw.println(CONTENT_PROVIDER_RETAIN_TIME);
         pw.print("  "); pw.print(KEY_GC_TIMEOUT); pw.print("=");
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index ff5efde..f440100 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -79,6 +79,7 @@
     static final boolean DEBUG_SAVED_STATE = DEBUG_ALL_ACTIVITIES || false;
     static final boolean DEBUG_SCREENSHOTS = DEBUG_ALL_ACTIVITIES || false;
     static final boolean DEBUG_SERVICE = DEBUG_ALL || false;
+    static final boolean DEBUG_FOREGROUND_SERVICE = DEBUG_ALL || false;
     static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false;
     static final boolean DEBUG_STACK = DEBUG_ALL || false;
     static final boolean DEBUG_STATES = DEBUG_ALL_ACTIVITIES || false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ad442c7..6202b91 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10323,11 +10323,11 @@
 
         if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToFront: moving taskId=" + taskId);
         synchronized(this) {
-            moveTaskToFrontLocked(taskId, flags, bOptions);
+            moveTaskToFrontLocked(taskId, flags, bOptions, false /* fromRecents */);
         }
     }
 
-    void moveTaskToFrontLocked(int taskId, int flags, Bundle bOptions) {
+    void moveTaskToFrontLocked(int taskId, int flags, Bundle bOptions, boolean fromRecents) {
         ActivityOptions options = ActivityOptions.fromBundle(bOptions);
 
         if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
@@ -10360,7 +10360,7 @@
                 // We are reshowing a task, use a starting window to hide the initial draw delay
                 // so the transition can start earlier.
                 topActivity.showStartingWindow(null /* prev */, false /* newTask */,
-                        true /* taskSwitch */);
+                        true /* taskSwitch */, fromRecents);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -12563,6 +12563,7 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
+        closeSystemDialogs("setLockScreenShown");
     }
 
     @Override
@@ -23815,9 +23816,10 @@
         }
 
         @Override
-        public void notifyAppTransitionStarting(SparseIntArray reasons) {
+        public void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp) {
             synchronized (ActivityManagerService.this) {
-                mStackSupervisor.mActivityMetricsLogger.notifyTransitionStarting(reasons);
+                mStackSupervisor.mActivityMetricsLogger.notifyTransitionStarting(
+                        reasons, timestamp);
             }
         }
 
@@ -24077,6 +24079,13 @@
                 if (reason != null) {
                     pw.println("  Reason: " + reason);
                 }
+                pw.println("  mLastHomeActivityStartResult: "
+                        + mActivityStarter.mLastHomeActivityStartResult);
+                final ActivityRecord r = mActivityStarter.mLastHomeActivityStartRecord[0];
+                if (r != null) {
+                    pw.println("  mLastHomeActivityStartRecord:");
+                    r.dump(pw, "   ");
+                }
                 pw.println();
                 dumpActivitiesLocked(null /* fd */, pw, null /* args */, 0 /* opti */,
                         true /* dumpAll */, false /* dumpClient */, null /* dumpPackage */,
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index bf7b663..98815d7 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -230,12 +230,12 @@
     /**
      * Notifies the tracker that all windows of the app have been drawn.
      */
-    void notifyWindowsDrawn(int stackId) {
+    void notifyWindowsDrawn(int stackId, long timestamp) {
         final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
         if (info == null || info.loggedWindowsDrawn) {
             return;
         }
-        info.windowsDrawnDelayMs = calculateCurrentDelay();
+        info.windowsDrawnDelayMs = calculateDelay(timestamp);
         info.loggedWindowsDrawn = true;
         if (allStacksWindowsDrawn() && mLoggedTransitionStarting) {
             reset(false /* abort */);
@@ -245,13 +245,13 @@
     /**
      * Notifies the tracker that the starting window was drawn.
      */
-    void notifyStartingWindowDrawn(int stackId) {
+    void notifyStartingWindowDrawn(int stackId, long timestamp) {
         final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
         if (info == null || info.loggedStartingWindowDrawn) {
             return;
         }
         info.loggedStartingWindowDrawn = true;
-        info.startingWindowDelayMs = calculateCurrentDelay();
+        info.startingWindowDelayMs = calculateDelay(timestamp);
     }
 
     /**
@@ -260,11 +260,11 @@
      * @param stackIdReasons A map from stack id to a reason integer, which must be on of
      *                       ActivityManagerInternal.APP_TRANSITION_* reasons.
      */
-    void notifyTransitionStarting(SparseIntArray stackIdReasons) {
+    void notifyTransitionStarting(SparseIntArray stackIdReasons, long timestamp) {
         if (!isAnyTransitionActive() || mLoggedTransitionStarting) {
             return;
         }
-        mCurrentTransitionDelayMs = calculateCurrentDelay();
+        mCurrentTransitionDelayMs = calculateDelay(timestamp);
         mLoggedTransitionStarting = true;
         for (int index = stackIdReasons.size() - 1; index >= 0; index--) {
             final int stackId = stackIdReasons.keyAt(index);
@@ -344,6 +344,11 @@
         return (int) (SystemClock.uptimeMillis() - mCurrentTransitionStartTime);
     }
 
+    private int calculateDelay(long timestamp) {
+        // Shouldn't take more than 25 days to launch an app, so int is fine here.
+        return (int) (timestamp - mCurrentTransitionStartTime);
+    }
+
     private void logAppTransitionMultiEvents() {
         for (int index = mStackTransitionInfo.size() - 1; index >= 0; index--) {
             final StackTransitionInfo info = mStackTransitionInfo.valueAt(index);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index ec6a4f6..68f4d0d 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1928,18 +1928,19 @@
     }
 
     @Override
-    public void onStartingWindowDrawn() {
+    public void onStartingWindowDrawn(long timestamp) {
         synchronized (service) {
-            mStackSupervisor.mActivityMetricsLogger.notifyStartingWindowDrawn(getStackId());
+            mStackSupervisor.mActivityMetricsLogger.notifyStartingWindowDrawn(
+                    getStackId(), timestamp);
         }
     }
 
     @Override
-    public void onWindowsDrawn() {
+    public void onWindowsDrawn(long timestamp) {
         synchronized (service) {
-            mStackSupervisor.mActivityMetricsLogger.notifyWindowsDrawn(getStackId());
+            mStackSupervisor.mActivityMetricsLogger.notifyWindowsDrawn(getStackId(), timestamp);
             if (displayStartTime != 0) {
-                reportLaunchTimeLocked(SystemClock.uptimeMillis());
+                reportLaunchTimeLocked(timestamp);
             }
             mStackSupervisor.sendWaitingVisibleReportLocked(this);
             startTime = 0;
@@ -2153,6 +2154,11 @@
     }
 
     void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
+        showStartingWindow(prev, newTask, taskSwitch, false /* fromRecents */);
+    }
+
+    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
+            boolean fromRecents) {
         if (mWindowContainerController == null) {
             return;
         }
@@ -2167,7 +2173,8 @@
                 compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                 prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
                 allowTaskSnapshot(),
-                state.ordinal() >= RESUMED.ordinal() && state.ordinal() <= STOPPED.ordinal());
+                state.ordinal() >= RESUMED.ordinal() && state.ordinal() <= STOPPED.ordinal(),
+                fromRecents);
         if (shown) {
             mStartingWindowState = STARTING_WINDOW_SHOWN;
         }
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 25dc354..5d5614c 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -5135,7 +5135,7 @@
                     && task.getRootActivity() != null) {
                 mService.mActivityStarter.sendPowerHintForLaunchStartIfNeeded(true /* forceSend */);
                 mActivityMetricsLogger.notifyActivityLaunching();
-                mService.moveTaskToFrontLocked(task.taskId, 0, bOptions);
+                mService.moveTaskToFrontLocked(task.taskId, 0, bOptions, true /* fromRecents */);
                 mActivityMetricsLogger.notifyActivityLaunched(ActivityManager.START_TASK_TO_FRONT,
                         task.getTopActivity());
 
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 1ed2ac1..d74d1d6 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -188,6 +188,11 @@
 
     private boolean mUsingVr2dDisplay;
 
+    // Last home activity record we attempted to start
+    final ActivityRecord[] mLastHomeActivityStartRecord = new ActivityRecord[1];
+    // The result of the last home activity we attempted to start.
+    int mLastHomeActivityStartResult;
+
     private void reset() {
         mStartActivity = null;
         mIntent = null;
@@ -592,12 +597,13 @@
 
     void startHomeActivityLocked(Intent intent, ActivityInfo aInfo, String reason) {
         mSupervisor.moveHomeStackTaskToTop(reason);
-        startActivityLocked(null /*caller*/, intent, null /*ephemeralIntent*/,
-                null /*resolvedType*/, aInfo, null /*rInfo*/, null /*voiceSession*/,
-                null /*voiceInteractor*/, null /*resultTo*/, null /*resultWho*/,
-                0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/, null /*callingPackage*/,
-                0 /*realCallingPid*/, 0 /*realCallingUid*/, 0 /*startFlags*/, null /*options*/,
-                false /*ignoreTargetSecurity*/, false /*componentSpecified*/, null /*outActivity*/,
+        mLastHomeActivityStartResult = startActivityLocked(null /*caller*/, intent,
+                null /*ephemeralIntent*/, null /*resolvedType*/, aInfo, null /*rInfo*/,
+                null /*voiceSession*/, null /*voiceInteractor*/, null /*resultTo*/,
+                null /*resultWho*/, 0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/,
+                null /*callingPackage*/, 0 /*realCallingPid*/, 0 /*realCallingUid*/,
+                0 /*startFlags*/, null /*options*/, false /*ignoreTargetSecurity*/,
+                false /*componentSpecified*/, mLastHomeActivityStartRecord /*outActivity*/,
                 null /*container*/, null /*inTask*/);
         if (mSupervisor.inResumeTopActivity) {
             // If we are in resume section already, home activity will be initialized, but not
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 1705b58..064ca58 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -614,9 +614,10 @@
             skip = true;
         }
 
-        if (!skip && (filter.receiverList.app == null || filter.receiverList.app.crashing)) {
+        if (!skip && (filter.receiverList.app == null || filter.receiverList.app.killed
+                || filter.receiverList.app.crashing)) {
             Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
-                    + " to " + filter.receiverList + ": process crashing");
+                    + " to " + filter.receiverList + ": process gone or crashing");
             skip = true;
         }
 
@@ -1317,7 +1318,7 @@
             }
 
             // Is this receiver's application already running?
-            if (app != null && app.thread != null) {
+            if (app != null && app.thread != null && !app.killed) {
                 try {
                     app.addPackage(info.activityInfo.packageName,
                             info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);
diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java
index 9970c82..b89586d 100644
--- a/services/core/java/com/android/server/am/UserState.java
+++ b/services/core/java/com/android/server/am/UserState.java
@@ -69,7 +69,6 @@
     public boolean setState(int oldState, int newState) {
         if (state == oldState) {
             setState(newState);
-            EventLogTags.writeAmUserStateChanged(mHandle.getIdentifier(), newState);
             return true;
         } else {
             Slog.w(TAG, "Expected user " + mHandle.getIdentifier() + " in state "
@@ -84,6 +83,7 @@
         }
         Slog.i(TAG, "User " + mHandle.getIdentifier() + " state changed from "
                 + stateToString(state) + " to " + stateToString(newState));
+        EventLogTags.writeAmUserStateChanged(mHandle.getIdentifier(), newState);
         lastState = state;
         state = newState;
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5a7f6e3..e5ab784 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -166,7 +166,6 @@
 
     /** debug calls to devices APIs */
     protected static final boolean DEBUG_DEVICES = Log.isLoggable(TAG + ".DEVICES", Log.DEBUG);
-
     /** How long to delay before persisting a change in volume/ringer mode. */
     private static final int PERSIST_DELAY = 500;
 
@@ -692,6 +691,7 @@
         // the mcc is read by onConfigureSafeVolume()
         mSafeMediaVolumeIndex = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_safe_media_volume_index) * 10;
+        mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
 
         mUseFixedVolume = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_useFixedVolume);
@@ -1347,7 +1347,7 @@
             // This is simulated by stepping by the full allowed volume range
             if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
                     (device & mSafeMediaVolumeDevices) != 0) {
-                step = mSafeMediaVolumeIndex;
+                step = safeMediaVolumeIndex(device);
             } else {
                 step = streamState.getMaxIndex();
             }
@@ -1693,7 +1693,7 @@
                 if (index != 0) {
                     if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
                             (device & mSafeMediaVolumeDevices) != 0) {
-                        index = mSafeMediaVolumeIndex;
+                        index = safeMediaVolumeIndex(device);
                     } else {
                         index = streamState.getMaxIndex();
                     }
@@ -3436,7 +3436,7 @@
                             MUSIC_ACTIVE_POLL_PERIOD_MS);
                     int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
                     if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) &&
-                            (index > mSafeMediaVolumeIndex)) {
+                            (index > safeMediaVolumeIndex(device))) {
                         // Approximate cumulative active music time
                         mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
                         if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
@@ -3454,12 +3454,40 @@
         mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
     }
 
+    private int getSafeUsbMediaVolumeIndex()
+    {
+        // determine UI volume index corresponding to the wanted safe gain in dBFS
+        int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
+        int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
+
+        while (Math.abs(max-min) > 1) {
+            int index = (max + min) / 2;
+            float gainDB = AudioSystem.getStreamVolumeDB(
+                    AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET);
+            if (gainDB == Float.NaN) {
+                //keep last min in case of read error
+                break;
+            } else if (gainDB == SAVE_VOLUME_GAIN_DBFS) {
+                min = index;
+                break;
+            } else if (gainDB < SAVE_VOLUME_GAIN_DBFS) {
+                min = index;
+            } else {
+                max = index;
+            }
+        }
+        return min * 10;
+    }
+
     private void onConfigureSafeVolume(boolean force, String caller) {
         synchronized (mSafeMediaVolumeState) {
             int mcc = mContext.getResources().getConfiguration().mcc;
             if ((mMcc != mcc) || ((mMcc == 0) && force)) {
                 mSafeMediaVolumeIndex = mContext.getResources().getInteger(
                         com.android.internal.R.integer.config_safe_media_volume_index) * 10;
+
+                mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+
                 boolean safeMediaVolumeEnabled =
                         SystemProperties.getBoolean("audio.safemedia.force", false)
                         || mContext.getResources().getBoolean(
@@ -5302,38 +5330,20 @@
         return delay;
     }
 
-    private void sendDeviceConnectionIntent(int device, int state, String address,
-            String deviceName) {
-        if (DEBUG_DEVICES) {
-            Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) +
-                    " state:0x" + Integer.toHexString(state) + " address:" + address +
-                    " name:" + deviceName + ");");
-        }
-        Intent intent = new Intent();
-
-        intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
-        intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
-        intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
-
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-
+    private void updateAudioRoutes(int device, int state)
+    {
         int connType = 0;
 
         if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
             connType = AudioRoutesInfo.MAIN_HEADSET;
-            intent.setAction(Intent.ACTION_HEADSET_PLUG);
-            intent.putExtra("microphone", 1);
         } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
                    device == AudioSystem.DEVICE_OUT_LINE) {
-            /*do apps care about line-out vs headphones?*/
             connType = AudioRoutesInfo.MAIN_HEADPHONES;
-            intent.setAction(Intent.ACTION_HEADSET_PLUG);
-            intent.putExtra("microphone", 0);
         } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
                 device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
             connType = AudioRoutesInfo.MAIN_HDMI;
-            configureHdmiPlugIntent(intent, state);
-        } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE) {
+        } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE||
+                device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
             connType = AudioRoutesInfo.MAIN_USB;
         }
 
@@ -5352,6 +5362,34 @@
                 }
             }
         }
+    }
+
+    private void sendDeviceConnectionIntent(int device, int state, String address,
+            String deviceName) {
+        if (DEBUG_DEVICES) {
+            Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) +
+                    " state:0x" + Integer.toHexString(state) + " address:" + address +
+                    " name:" + deviceName + ");");
+        }
+        Intent intent = new Intent();
+
+        if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
+            intent.setAction(Intent.ACTION_HEADSET_PLUG);
+            intent.putExtra("microphone", 1);
+        } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
+                   device == AudioSystem.DEVICE_OUT_LINE) {
+            intent.setAction(Intent.ACTION_HEADSET_PLUG);
+            intent.putExtra("microphone", 0);
+        } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
+                device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
+            configureHdmiPlugIntent(intent, state);
+        }
+
+        intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
+        intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
+        intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
+
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
 
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -5425,6 +5463,7 @@
             if (!isUsb && device != AudioSystem.DEVICE_IN_WIRED_HEADSET) {
                 sendDeviceConnectionIntent(device, state, address, deviceName);
             }
+            updateAudioRoutes(device, state);
         }
     }
 
@@ -5950,9 +5989,15 @@
     private int mMcc = 0;
     // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property
     private int mSafeMediaVolumeIndex;
+    // mSafeUsbMediaVolumeIndex is used for USB Headsets and is to the music volume
+    // UI index corresponding to a gain of -15 dBFS. This corresponds to a loudness of 85 dB SPL
+    // if the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
+    private int mSafeUsbMediaVolumeIndex;
+    private static final float SAVE_VOLUME_GAIN_DBFS = -15;
     // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
     private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET |
-                                                AudioSystem.DEVICE_OUT_WIRED_HEADPHONE;
+                                                AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
+                                                AudioSystem.DEVICE_OUT_USB_HEADSET;
     // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
     // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
     // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
@@ -5961,6 +6006,17 @@
     private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000;  // 1 minute polling interval
     private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000;  // 30s after boot completed
 
+    private int safeMediaVolumeIndex(int device) {
+        if ((device & mSafeMediaVolumeDevices) == 0) {
+            return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
+        }
+        if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
+            return mSafeUsbMediaVolumeIndex;
+        } else {
+            return mSafeMediaVolumeIndex;
+        }
+    }
+
     private void setSafeMediaVolumeEnabled(boolean on, String caller) {
         synchronized (mSafeMediaVolumeState) {
             if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) &&
@@ -5995,8 +6051,8 @@
                 continue;
             }
             int index = streamState.getIndex(device);
-            if (index > mSafeMediaVolumeIndex) {
-                streamState.setIndex(mSafeMediaVolumeIndex, device, caller);
+            if (index > safeMediaVolumeIndex(device)) {
+                streamState.setIndex(safeMediaVolumeIndex(device), device, caller);
                 sendMsg(mAudioHandler,
                         MSG_SET_DEVICE_VOLUME,
                         SENDMSG_QUEUE,
@@ -6014,7 +6070,7 @@
             if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) &&
                     (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) &&
                     ((device & mSafeMediaVolumeDevices) != 0) &&
-                    (index > mSafeMediaVolumeIndex)) {
+                    (index > safeMediaVolumeIndex(device))) {
                 return false;
             }
             return true;
@@ -6240,6 +6296,7 @@
         pw.print("  mSafeMediaVolumeState=");
         pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
         pw.print("  mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
+        pw.print("  mSafeUsbMediaVolumeIndex="); pw.println(mSafeUsbMediaVolumeIndex);
         pw.print("  sIndependentA11yVolume="); pw.println(sIndependentA11yVolume);
         pw.print("  mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
         pw.print("  mMusicActiveMs="); pw.println(mMusicActiveMs);
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index e6923d5..37ef132 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -88,7 +88,6 @@
 import com.android.internal.util.StateMachine;
 import com.android.server.connectivity.tethering.IControlsTethering;
 import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
-import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices;
 import com.android.server.connectivity.tethering.OffloadController;
 import com.android.server.connectivity.tethering.SimChangeListener;
 import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
@@ -1248,9 +1247,9 @@
             protected void chooseUpstreamType(boolean tryCell) {
                 updateConfiguration(); // TODO - remove?
 
-                final int upstreamType = findPreferredUpstreamType(
-                        getConnectivityManager(), mConfig);
-                if (upstreamType == ConnectivityManager.TYPE_NONE) {
+                final NetworkState ns = mUpstreamNetworkMonitor.selectPreferredUpstreamType(
+                        mConfig.preferredUpstreamIfaceTypes);
+                if (ns == null) {
                     if (tryCell) {
                         mUpstreamNetworkMonitor.registerMobileNetworkRequest();
                         // We think mobile should be coming up; don't set a retry.
@@ -1258,92 +1257,30 @@
                         sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
                     }
                 }
-                setUpstreamByType(upstreamType);
+                setUpstreamByType(ns);
             }
 
-            // TODO: Move this function into UpstreamNetworkMonitor.
-            protected int findPreferredUpstreamType(ConnectivityManager cm,
-                                                    TetheringConfiguration cfg) {
-                int upType = ConnectivityManager.TYPE_NONE;
-
-                if (VDBG) {
-                    Log.d(TAG, "chooseUpstreamType has upstream iface types:");
-                    for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
-                        Log.d(TAG, " " + netType);
-                    }
-                }
-
-                for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
-                    NetworkInfo info = cm.getNetworkInfo(netType.intValue());
-                    // TODO: if the network is suspended we should consider
-                    // that to be the same as connected here.
-                    if ((info != null) && info.isConnected()) {
-                        upType = netType.intValue();
-                        break;
-                    }
-                }
-
-                final int preferredUpstreamMobileApn = cfg.isDunRequired
-                        ? ConnectivityManager.TYPE_MOBILE_DUN
-                        : ConnectivityManager.TYPE_MOBILE_HIPRI;
-                mLog.log(String.format(
-                        "findPreferredUpstreamType(), preferredApn=%s, got type=%s",
-                        getNetworkTypeName(preferredUpstreamMobileApn),
-                        getNetworkTypeName(upType)));
-
-                switch (upType) {
-                    case ConnectivityManager.TYPE_MOBILE_DUN:
-                    case ConnectivityManager.TYPE_MOBILE_HIPRI:
-                        // If we're on DUN, put our own grab on it.
-                        mUpstreamNetworkMonitor.registerMobileNetworkRequest();
-                        break;
-                    case ConnectivityManager.TYPE_NONE:
-                        break;
-                    default:
-                        /* If we've found an active upstream connection that's not DUN/HIPRI
-                         * we should stop any outstanding DUN/HIPRI start requests.
-                         *
-                         * If we found NONE we don't want to do this as we want any previous
-                         * requests to keep trying to bring up something we can use.
-                         */
-                        mUpstreamNetworkMonitor.releaseMobileNetworkRequest();
-                        break;
-                }
-
-                return upType;
-            }
-
-            protected void setUpstreamByType(int upType) {
-                final ConnectivityManager cm = getConnectivityManager();
-                Network network = null;
+            protected void setUpstreamByType(NetworkState ns) {
                 String iface = null;
-                if (upType != ConnectivityManager.TYPE_NONE) {
-                    LinkProperties linkProperties = cm.getLinkProperties(upType);
-                    if (linkProperties != null) {
-                        // Find the interface with the default IPv4 route. It may be the
-                        // interface described by linkProperties, or one of the interfaces
-                        // stacked on top of it.
-                        Log.i(TAG, "Finding IPv4 upstream interface on: " + linkProperties);
-                        RouteInfo ipv4Default = RouteInfo.selectBestRoute(
-                            linkProperties.getAllRoutes(), Inet4Address.ANY);
-                        if (ipv4Default != null) {
-                            iface = ipv4Default.getInterface();
-                            Log.i(TAG, "Found interface " + ipv4Default.getInterface());
-                        } else {
-                            Log.i(TAG, "No IPv4 upstream interface, giving up.");
-                        }
+                if (ns != null && ns.linkProperties != null) {
+                    // Find the interface with the default IPv4 route. It may be the
+                    // interface described by linkProperties, or one of the interfaces
+                    // stacked on top of it.
+                    Log.i(TAG, "Finding IPv4 upstream interface on: " + ns.linkProperties);
+                    RouteInfo ipv4Default = RouteInfo.selectBestRoute(
+                        ns.linkProperties.getAllRoutes(), Inet4Address.ANY);
+                    if (ipv4Default != null) {
+                        iface = ipv4Default.getInterface();
+                        Log.i(TAG, "Found interface " + ipv4Default.getInterface());
+                    } else {
+                        Log.i(TAG, "No IPv4 upstream interface, giving up.");
                     }
+                }
 
-                    if (iface != null) {
-                        network = cm.getNetworkForType(upType);
-                        if (network == null) {
-                            Log.e(TAG, "No Network for upstream type " + upType + "!");
-                        }
-                        setDnsForwarders(network, linkProperties);
-                    }
+                if (iface != null) {
+                    setDnsForwarders(ns.network, ns.linkProperties);
                 }
                 notifyTetheredOfNewUpstreamIface(iface);
-                NetworkState ns = mUpstreamNetworkMonitor.lookup(network);
                 if (ns != null && pertainsToCurrentUpstream(ns)) {
                     // If we already have NetworkState for this network examine
                     // it immediately, because there likely will be no second
@@ -1865,8 +1802,7 @@
         final TetherState tetherState = new TetherState(
                 new TetherInterfaceStateMachine(
                     iface, mLooper, interfaceType, mLog, mNMService, mStatsService,
-                    makeControlCallback(iface),
-                    new IPv6TetheringInterfaceServices(iface, mNMService, mLog)));
+                    makeControlCallback(iface)));
         mTetherStates.put(iface, tetherState);
         tetherState.stateMachine.start();
     }
diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
deleted file mode 100644
index adf4af8..0000000
--- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity.tethering;
-
-import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
-
-import android.net.INetd;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
-import android.net.NetworkState;
-import android.net.RouteInfo;
-import android.net.ip.RouterAdvertisementDaemon;
-import android.net.ip.RouterAdvertisementDaemon.RaParams;
-import android.net.util.NetdService;
-import android.net.util.SharedLog;
-import android.os.INetworkManagementService;
-import android.os.ServiceSpecificException;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Random;
-
-
-/**
- * @hide
- */
-public class IPv6TetheringInterfaceServices {
-    private static final String TAG = IPv6TetheringInterfaceServices.class.getSimpleName();
-    private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64");
-
-    private final String mIfName;
-    private final INetworkManagementService mNMService;
-    private final SharedLog mLog;
-
-    private NetworkInterface mNetworkInterface;
-    private byte[] mHwAddr;
-    private LinkProperties mLastIPv6LinkProperties;
-    private RouterAdvertisementDaemon mRaDaemon;
-    private RaParams mLastRaParams;
-
-    public IPv6TetheringInterfaceServices(
-            String ifname, INetworkManagementService nms, SharedLog log) {
-        mIfName = ifname;
-        mNMService = nms;
-        mLog = log.forSubComponent(mIfName);
-    }
-
-    public boolean start() {
-        // TODO: Refactor for testability (perhaps passing an android.system.Os
-        // instance and calling getifaddrs() directly).
-        try {
-            mNetworkInterface = NetworkInterface.getByName(mIfName);
-        } catch (SocketException e) {
-            mLog.e("Error looking up NetworkInterfaces: " + e);
-            stop();
-            return false;
-        }
-        if (mNetworkInterface == null) {
-            mLog.e("Failed to find NetworkInterface");
-            stop();
-            return false;
-        }
-
-        try {
-            mHwAddr = mNetworkInterface.getHardwareAddress();
-        } catch (SocketException e) {
-            mLog.e("Failed to find hardware address: " + e);
-            stop();
-            return false;
-        }
-
-        final int ifindex = mNetworkInterface.getIndex();
-        mRaDaemon = new RouterAdvertisementDaemon(mIfName, ifindex, mHwAddr);
-        if (!mRaDaemon.start()) {
-            stop();
-            return false;
-        }
-
-        return true;
-    }
-
-    public void stop() {
-        mNetworkInterface = null;
-        mHwAddr = null;
-        setRaParams(null);
-
-        if (mRaDaemon != null) {
-            mRaDaemon.stop();
-            mRaDaemon = null;
-        }
-    }
-
-    // IPv6TetheringCoordinator sends updates with carefully curated IPv6-only
-    // LinkProperties. These have extraneous data filtered out and only the
-    // necessary prefixes included (per its prefix distribution policy).
-    //
-    // TODO: Evaluate using a data structure than is more directly suited to
-    // communicating only the relevant information.
-    public void updateUpstreamIPv6LinkProperties(LinkProperties v6only) {
-        if (mRaDaemon == null) return;
-
-        // Avoid unnecessary work on spurious updates.
-        if (Objects.equals(mLastIPv6LinkProperties, v6only)) {
-            return;
-        }
-
-        RaParams params = null;
-
-        if (v6only != null) {
-            params = new RaParams();
-            params.mtu = v6only.getMtu();
-            params.hasDefaultRoute = v6only.hasIPv6DefaultRoute();
-
-            for (LinkAddress linkAddr : v6only.getLinkAddresses()) {
-                if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue;
-
-                final IpPrefix prefix = new IpPrefix(
-                        linkAddr.getAddress(), linkAddr.getPrefixLength());
-                params.prefixes.add(prefix);
-
-                final Inet6Address dnsServer = getLocalDnsIpFor(prefix);
-                if (dnsServer != null) {
-                    params.dnses.add(dnsServer);
-                }
-            }
-        }
-        // If v6only is null, we pass in null to setRaParams(), which handles
-        // deprecation of any existing RA data.
-
-        setRaParams(params);
-        mLastIPv6LinkProperties = v6only;
-    }
-
-
-    private void configureLocalRoutes(
-            HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) {
-        // [1] Remove the routes that are deprecated.
-        if (!deprecatedPrefixes.isEmpty()) {
-            final ArrayList<RouteInfo> toBeRemoved = getLocalRoutesFor(deprecatedPrefixes);
-            try {
-                final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved);
-                if (removalFailures > 0) {
-                    mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
-                            removalFailures));
-                }
-            } catch (RemoteException e) {
-                mLog.e("Failed to remove IPv6 routes from local table: " + e);
-            }
-        }
-
-        // [2] Add only the routes that have not previously been added.
-        if (newPrefixes != null && !newPrefixes.isEmpty()) {
-            HashSet<IpPrefix> addedPrefixes = (HashSet) newPrefixes.clone();
-            if (mLastRaParams != null) {
-                addedPrefixes.removeAll(mLastRaParams.prefixes);
-            }
-
-            if (mLastRaParams == null || mLastRaParams.prefixes.isEmpty()) {
-                // We need to be able to send unicast RAs, and clients might
-                // like to ping the default router's link-local address.  Note
-                // that we never remove the link-local route from the network
-                // until Tethering disables tethering on the interface. We
-                // only need to add the link-local prefix once, but in the
-                // event we add it more than once netd silently ignores EEXIST.
-                addedPrefixes.add(LINK_LOCAL_PREFIX);
-            }
-
-            if (!addedPrefixes.isEmpty()) {
-                final ArrayList<RouteInfo> toBeAdded = getLocalRoutesFor(addedPrefixes);
-                try {
-                    // It's safe to call addInterfaceToLocalNetwork() even if
-                    // the interface is already in the local_network. Note also
-                    // that adding routes that already exist does not cause an
-                    // error (EEXIST is silently ignored).
-                    mNMService.addInterfaceToLocalNetwork(mIfName, toBeAdded);
-                } catch (RemoteException e) {
-                    mLog.e("Failed to add IPv6 routes to local table: " + e);
-                }
-            }
-        }
-    }
-
-    private void configureLocalDns(
-            HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) {
-        final INetd netd = NetdService.getInstance();
-        if (netd == null) {
-            if (newDnses != null) newDnses.clear();
-            mLog.e("No netd service instance available; not setting local IPv6 addresses");
-            return;
-        }
-
-        // [1] Remove deprecated local DNS IP addresses.
-        if (!deprecatedDnses.isEmpty()) {
-            for (Inet6Address dns : deprecatedDnses) {
-                final String dnsString = dns.getHostAddress();
-                try {
-                    netd.interfaceDelAddress(mIfName, dnsString, RFC7421_PREFIX_LENGTH);
-                } catch (ServiceSpecificException | RemoteException e) {
-                    mLog.e("Failed to remove local dns IP " + dnsString + ": " + e);
-                }
-            }
-        }
-
-        // [2] Add only the local DNS IP addresses that have not previously been added.
-        if (newDnses != null && !newDnses.isEmpty()) {
-            final HashSet<Inet6Address> addedDnses = (HashSet) newDnses.clone();
-            if (mLastRaParams != null) {
-                addedDnses.removeAll(mLastRaParams.dnses);
-            }
-
-            for (Inet6Address dns : addedDnses) {
-                final String dnsString = dns.getHostAddress();
-                try {
-                    netd.interfaceAddAddress(mIfName, dnsString, RFC7421_PREFIX_LENGTH);
-                } catch (ServiceSpecificException | RemoteException e) {
-                    mLog.e("Failed to add local dns IP " + dnsString + ": " + e);
-                    newDnses.remove(dns);
-                }
-            }
-        }
-
-        try {
-            netd.tetherApplyDnsInterfaces();
-        } catch (ServiceSpecificException | RemoteException e) {
-            mLog.e("Failed to update local DNS caching server");
-            if (newDnses != null) newDnses.clear();
-        }
-    }
-
-    private void setRaParams(RaParams newParams) {
-        if (mRaDaemon != null) {
-            final RaParams deprecatedParams =
-                    RaParams.getDeprecatedRaParams(mLastRaParams, newParams);
-
-            configureLocalRoutes(deprecatedParams.prefixes,
-                    (newParams != null) ? newParams.prefixes : null);
-
-            configureLocalDns(deprecatedParams.dnses,
-                    (newParams != null) ? newParams.dnses : null);
-
-            mRaDaemon.buildNewRa(deprecatedParams, newParams);
-        }
-
-        mLastRaParams = newParams;
-    }
-
-    // Accumulate routes representing "prefixes to be assigned to the local
-    // interface", for subsequent modification of local_network routing.
-    private ArrayList<RouteInfo> getLocalRoutesFor(HashSet<IpPrefix> prefixes) {
-        final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>();
-        for (IpPrefix ipp : prefixes) {
-            localRoutes.add(new RouteInfo(ipp, null, mIfName));
-        }
-        return localRoutes;
-    }
-
-    // Given a prefix like 2001:db8::/64 return an address like 2001:db8::1.
-    private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) {
-        final byte[] dnsBytes = localPrefix.getRawAddress();
-        dnsBytes[dnsBytes.length - 1] = getRandomNonZeroByte();
-        try {
-            return Inet6Address.getByAddress(null, dnsBytes, 0);
-        } catch (UnknownHostException e) {
-            Slog.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix);
-            return null;
-        }
-    }
-
-    private static byte getRandomNonZeroByte() {
-        final byte random = (byte) (new Random()).nextInt();
-        // Don't pick the subnet-router anycast address, since that might be
-        // in use on the upstream already.
-        return (random != 0) ? random : 0x1;
-    }
-}
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index 3aca45f..ce6b8be 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -161,6 +161,7 @@
             }
         }
 
-        return mHwInterface.setUpstreamParameters(iface, v4addr, v4gateway, v6gateways);
+        return mHwInterface.setUpstreamParameters(
+                iface, v4addr, v4gateway, (v6gateways.isEmpty() ? null : v6gateways));
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 3ecf0d1..1fc1684 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity.tethering;
 
+import static com.android.internal.util.BitUtils.uint16;
+
 import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
 import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
 import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
@@ -33,6 +35,9 @@
  */
 public class OffloadHardwareInterface {
     private static final String TAG = OffloadHardwareInterface.class.getSimpleName();
+    private static final String NO_INTERFACE_NAME = "";
+    private static final String NO_IPV4_ADDRESS = "";
+    private static final String NO_IPV4_GATEWAY = "";
 
     private static native boolean configOffload();
 
@@ -107,6 +112,11 @@
 
     public boolean setUpstreamParameters(
             String iface, String v4addr, String v4gateway, ArrayList<String> v6gws) {
+        iface = iface != null ? iface : NO_INTERFACE_NAME;
+        v4addr = v4addr != null ? v4addr : NO_IPV4_ADDRESS;
+        v4gateway = v4gateway != null ? v4gateway : NO_IPV4_GATEWAY;
+        v6gws = v6gws != null ? v6gws : new ArrayList<>();
+
         final CbResults results = new CbResults();
         try {
             mOffloadControl.setUpstreamParameters(
@@ -143,8 +153,8 @@
             handler.post(() -> {
                     controlCb.onNatTimeoutUpdate(
                         params.proto,
-                        params.src.addr, params.src.port,
-                        params.dst.addr, params.dst.port);
+                        params.src.addr, uint16(params.src.port),
+                        params.dst.addr, uint16(params.dst.port));
             });
         }
     }
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 82b9ca0..b88361f 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -16,17 +16,28 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
+
 import android.net.ConnectivityManager;
+import android.net.INetd;
 import android.net.INetworkStatsService;
 import android.net.InterfaceConfiguration;
+import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.net.ip.RouterAdvertisementDaemon;
+import android.net.ip.RouterAdvertisementDaemon.RaParams;
+import android.net.util.NetdService;
 import android.net.util.SharedLog;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.util.MessageUtils;
@@ -34,7 +45,15 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
+import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Random;
 
 /**
  * @hide
@@ -42,6 +61,8 @@
  * Tracks the eligibility of a given network interface for tethering.
  */
 public class TetherInterfaceStateMachine extends StateMachine {
+    private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64");
+
     private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129";
     private static final int USB_PREFIX_LENGTH = 24;
     private static final String WIFI_HOST_IFACE_ADDR = "192.168.43.1";
@@ -91,15 +112,19 @@
     private final String mIfaceName;
     private final int mInterfaceType;
     private final LinkProperties mLinkProperties;
-    private final IPv6TetheringInterfaceServices mIPv6TetherSvc;
 
     private int mLastError;
     private String mMyUpstreamIfaceName;  // may change over time
+    private NetworkInterface mNetworkInterface;
+    private byte[] mHwAddr;
+    private LinkProperties mLastIPv6LinkProperties;
+    private RouterAdvertisementDaemon mRaDaemon;
+    private RaParams mLastRaParams;
 
     public TetherInterfaceStateMachine(
             String ifaceName, Looper looper, int interfaceType, SharedLog log,
             INetworkManagementService nMService, INetworkStatsService statsService,
-            IControlsTethering tetherController, IPv6TetheringInterfaceServices ipv6Svc) {
+            IControlsTethering tetherController) {
         super(ifaceName, looper);
         mLog = log.forSubComponent(ifaceName);
         mNMService = nMService;
@@ -109,7 +134,6 @@
         mInterfaceType = interfaceType;
         mLinkProperties = new LinkProperties();
         mLinkProperties.setInterfaceName(mIfaceName);
-        mIPv6TetherSvc = ipv6Svc;
         mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
 
         mInitialState = new InitialState();
@@ -132,6 +156,10 @@
 
     public void stop() { sendMessage(CMD_INTERFACE_DOWN); }
 
+    /**
+     * Internals.
+     */
+
     // configured when we start tethering and unconfig'd on error or conclusion
     private boolean configureIfaceIp(boolean enabled) {
         if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")");
@@ -178,6 +206,206 @@
         return true;
     }
 
+    private boolean startIPv6() {
+        // TODO: Refactor for testability (perhaps passing an android.system.Os
+        // instance and calling getifaddrs() directly).
+        try {
+            mNetworkInterface = NetworkInterface.getByName(mIfaceName);
+        } catch (SocketException e) {
+            mLog.e("Error looking up NetworkInterfaces: " + e);
+            stopIPv6();
+            return false;
+        }
+        if (mNetworkInterface == null) {
+            mLog.e("Failed to find NetworkInterface");
+            stopIPv6();
+            return false;
+        }
+
+        try {
+            mHwAddr = mNetworkInterface.getHardwareAddress();
+        } catch (SocketException e) {
+            mLog.e("Failed to find hardware address: " + e);
+            stopIPv6();
+            return false;
+        }
+
+        final int ifindex = mNetworkInterface.getIndex();
+        mRaDaemon = new RouterAdvertisementDaemon(mIfaceName, ifindex, mHwAddr);
+        if (!mRaDaemon.start()) {
+            stopIPv6();
+            return false;
+        }
+
+        return true;
+    }
+
+    private void stopIPv6() {
+        mNetworkInterface = null;
+        mHwAddr = null;
+        setRaParams(null);
+
+        if (mRaDaemon != null) {
+            mRaDaemon.stop();
+            mRaDaemon = null;
+        }
+    }
+
+    // IPv6TetheringCoordinator sends updates with carefully curated IPv6-only
+    // LinkProperties. These have extraneous data filtered out and only the
+    // necessary prefixes included (per its prefix distribution policy).
+    //
+    // TODO: Evaluate using a data structure than is more directly suited to
+    // communicating only the relevant information.
+    private void updateUpstreamIPv6LinkProperties(LinkProperties v6only) {
+        if (mRaDaemon == null) return;
+
+        // Avoid unnecessary work on spurious updates.
+        if (Objects.equals(mLastIPv6LinkProperties, v6only)) {
+            return;
+        }
+
+        RaParams params = null;
+
+        if (v6only != null) {
+            params = new RaParams();
+            params.mtu = v6only.getMtu();
+            params.hasDefaultRoute = v6only.hasIPv6DefaultRoute();
+
+            for (LinkAddress linkAddr : v6only.getLinkAddresses()) {
+                if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue;
+
+                final IpPrefix prefix = new IpPrefix(
+                        linkAddr.getAddress(), linkAddr.getPrefixLength());
+                params.prefixes.add(prefix);
+
+                final Inet6Address dnsServer = getLocalDnsIpFor(prefix);
+                if (dnsServer != null) {
+                    params.dnses.add(dnsServer);
+                }
+            }
+        }
+        // If v6only is null, we pass in null to setRaParams(), which handles
+        // deprecation of any existing RA data.
+
+        setRaParams(params);
+        mLastIPv6LinkProperties = v6only;
+    }
+
+    private void configureLocalRoutes(
+            HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) {
+        // [1] Remove the routes that are deprecated.
+        if (!deprecatedPrefixes.isEmpty()) {
+            final ArrayList<RouteInfo> toBeRemoved =
+                    getLocalRoutesFor(mIfaceName, deprecatedPrefixes);
+            try {
+                final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved);
+                if (removalFailures > 0) {
+                    mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
+                            removalFailures));
+                }
+            } catch (RemoteException e) {
+                mLog.e("Failed to remove IPv6 routes from local table: " + e);
+            }
+        }
+
+        // [2] Add only the routes that have not previously been added.
+        if (newPrefixes != null && !newPrefixes.isEmpty()) {
+            HashSet<IpPrefix> addedPrefixes = (HashSet) newPrefixes.clone();
+            if (mLastRaParams != null) {
+                addedPrefixes.removeAll(mLastRaParams.prefixes);
+            }
+
+            if (mLastRaParams == null || mLastRaParams.prefixes.isEmpty()) {
+                // We need to be able to send unicast RAs, and clients might
+                // like to ping the default router's link-local address.  Note
+                // that we never remove the link-local route from the network
+                // until Tethering disables tethering on the interface. We
+                // only need to add the link-local prefix once, but in the
+                // event we add it more than once netd silently ignores EEXIST.
+                addedPrefixes.add(LINK_LOCAL_PREFIX);
+            }
+
+            if (!addedPrefixes.isEmpty()) {
+                final ArrayList<RouteInfo> toBeAdded =
+                        getLocalRoutesFor(mIfaceName, addedPrefixes);
+                try {
+                    // It's safe to call addInterfaceToLocalNetwork() even if
+                    // the interface is already in the local_network. Note also
+                    // that adding routes that already exist does not cause an
+                    // error (EEXIST is silently ignored).
+                    mNMService.addInterfaceToLocalNetwork(mIfaceName, toBeAdded);
+                } catch (RemoteException e) {
+                    mLog.e("Failed to add IPv6 routes to local table: " + e);
+                }
+            }
+        }
+    }
+
+    private void configureLocalDns(
+            HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) {
+        final INetd netd = NetdService.getInstance();
+        if (netd == null) {
+            if (newDnses != null) newDnses.clear();
+            mLog.e("No netd service instance available; not setting local IPv6 addresses");
+            return;
+        }
+
+        // [1] Remove deprecated local DNS IP addresses.
+        if (!deprecatedDnses.isEmpty()) {
+            for (Inet6Address dns : deprecatedDnses) {
+                final String dnsString = dns.getHostAddress();
+                try {
+                    netd.interfaceDelAddress(mIfaceName, dnsString, RFC7421_PREFIX_LENGTH);
+                } catch (ServiceSpecificException | RemoteException e) {
+                    mLog.e("Failed to remove local dns IP " + dnsString + ": " + e);
+                }
+            }
+        }
+
+        // [2] Add only the local DNS IP addresses that have not previously been added.
+        if (newDnses != null && !newDnses.isEmpty()) {
+            final HashSet<Inet6Address> addedDnses = (HashSet) newDnses.clone();
+            if (mLastRaParams != null) {
+                addedDnses.removeAll(mLastRaParams.dnses);
+            }
+
+            for (Inet6Address dns : addedDnses) {
+                final String dnsString = dns.getHostAddress();
+                try {
+                    netd.interfaceAddAddress(mIfaceName, dnsString, RFC7421_PREFIX_LENGTH);
+                } catch (ServiceSpecificException | RemoteException e) {
+                    mLog.e("Failed to add local dns IP " + dnsString + ": " + e);
+                    newDnses.remove(dns);
+                }
+            }
+        }
+
+        try {
+            netd.tetherApplyDnsInterfaces();
+        } catch (ServiceSpecificException | RemoteException e) {
+            mLog.e("Failed to update local DNS caching server");
+            if (newDnses != null) newDnses.clear();
+        }
+    }
+
+    private void setRaParams(RaParams newParams) {
+        if (mRaDaemon != null) {
+            final RaParams deprecatedParams =
+                    RaParams.getDeprecatedRaParams(mLastRaParams, newParams);
+
+            configureLocalRoutes(deprecatedParams.prefixes,
+                    (newParams != null) ? newParams.prefixes : null);
+
+            configureLocalDns(deprecatedParams.dnses,
+                    (newParams != null) ? newParams.dnses : null);
+
+            mRaDaemon.buildNewRa(deprecatedParams, newParams);
+        }
+
+        mLastRaParams = newParams;
+    }
+
     private void maybeLogMessage(State state, int what) {
         if (DBG) {
             Log.d(TAG, state.getName() + " got " +
@@ -222,8 +450,7 @@
                     transitionTo(mUnavailableState);
                     break;
                 case CMD_IPV6_TETHER_UPDATE:
-                    mIPv6TetherSvc.updateUpstreamIPv6LinkProperties(
-                            (LinkProperties) message.obj);
+                    updateUpstreamIPv6LinkProperties((LinkProperties) message.obj);
                     break;
                 default:
                     return NOT_HANDLED;
@@ -248,8 +475,8 @@
                 return;
             }
 
-            if (!mIPv6TetherSvc.start()) {
-                mLog.e("Failed to start IPv6TetheringInterfaceServices");
+            if (!startIPv6()) {
+                mLog.e("Failed to startIPv6");
                 // TODO: Make this a fatal error once Bluetooth IPv6 is sorted.
                 return;
             }
@@ -260,7 +487,7 @@
             // Note that at this point, we're leaving the tethered state.  We can fail any
             // of these operations, but it doesn't really change that we have to try them
             // all in sequence.
-            mIPv6TetherSvc.stop();
+            stopIPv6();
 
             try {
                 mNMService.untetherInterface(mIfaceName);
@@ -285,8 +512,7 @@
                     if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName);
                     break;
                 case CMD_IPV6_TETHER_UPDATE:
-                    mIPv6TetherSvc.updateUpstreamIPv6LinkProperties(
-                            (LinkProperties) message.obj);
+                    updateUpstreamIPv6LinkProperties((LinkProperties) message.obj);
                     break;
                 case CMD_IP_FORWARDING_ENABLE_ERROR:
                 case CMD_IP_FORWARDING_DISABLE_ERROR:
@@ -448,4 +674,34 @@
             sendInterfaceState(IControlsTethering.STATE_UNAVAILABLE);
         }
     }
+
+    // Accumulate routes representing "prefixes to be assigned to the local
+    // interface", for subsequent modification of local_network routing.
+    private static ArrayList<RouteInfo> getLocalRoutesFor(
+            String ifname, HashSet<IpPrefix> prefixes) {
+        final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>();
+        for (IpPrefix ipp : prefixes) {
+            localRoutes.add(new RouteInfo(ipp, null, ifname));
+        }
+        return localRoutes;
+    }
+
+    // Given a prefix like 2001:db8::/64 return an address like 2001:db8::1.
+    private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) {
+        final byte[] dnsBytes = localPrefix.getRawAddress();
+        dnsBytes[dnsBytes.length - 1] = getRandomNonZeroByte();
+        try {
+            return Inet6Address.getByAddress(null, dnsBytes, 0);
+        } catch (UnknownHostException e) {
+            Slog.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix);
+            return null;
+        }
+    }
+
+    private static byte getRandomNonZeroByte() {
+        final byte random = (byte) (new Random()).nextInt();
+        // Don't pick the subnet-router anycast address, since that might be
+        // in use on the upstream already.
+        return (random != 0) ? random : 0x1;
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index cd6038f..9ebfaf7 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.ConnectivityManager.getNetworkTypeName;
+import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
 
@@ -172,8 +174,39 @@
         mMobileNetworkCallback = null;
     }
 
-    public NetworkState lookup(Network network) {
-        return (network != null) ? mNetworkMap.get(network) : null;
+    // So many TODOs here, but chief among them is: make this functionality an
+    // integral part of this class such that whenever a higher priority network
+    // becomes available and useful we (a) file a request to keep it up as
+    // necessary and (b) change all upstream tracking state accordingly (by
+    // passing LinkProperties up to Tethering).
+    //
+    // Next TODO: return NetworkState instead of just the type.
+    public NetworkState selectPreferredUpstreamType(Iterable<Integer> preferredTypes) {
+        final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType(
+                mNetworkMap.values(), preferredTypes);
+
+        mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type));
+
+        switch (typeStatePair.type) {
+            case TYPE_MOBILE_DUN:
+            case TYPE_MOBILE_HIPRI:
+                // If we're on DUN, put our own grab on it.
+                registerMobileNetworkRequest();
+                break;
+            case TYPE_NONE:
+                break;
+            default:
+                /* If we've found an active upstream connection that's not DUN/HIPRI
+                 * we should stop any outstanding DUN/HIPRI start requests.
+                 *
+                 * If we found NONE we don't want to do this as we want any previous
+                 * requests to keep trying to bring up something we can use.
+                 */
+                releaseMobileNetworkRequest();
+                break;
+        }
+
+        return typeStatePair.ns;
     }
 
     private void handleAvailable(int callbackType, Network network) {
@@ -365,4 +398,37 @@
     private void notifyTarget(int which, NetworkState netstate) {
         mTarget.sendMessage(mWhat, which, 0, netstate);
     }
+
+    static private class TypeStatePair {
+        public int type = TYPE_NONE;
+        public NetworkState ns = null;
+    }
+
+    static private TypeStatePair findFirstAvailableUpstreamByType(
+            Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) {
+        final TypeStatePair result = new TypeStatePair();
+
+        for (int type : preferredTypes) {
+            NetworkCapabilities nc;
+            try {
+                nc = ConnectivityManager.networkCapabilitiesForType(type);
+            } catch (IllegalArgumentException iae) {
+                Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " +
+                       ConnectivityManager.getNetworkTypeName(type));
+                continue;
+            }
+
+            for (NetworkState value : netStates) {
+                if (!nc.satisfiedByNetworkCapabilities(value.networkCapabilities)) {
+                    continue;
+                }
+
+                result.type = type;
+                result.ns = value;
+                return result;
+            }
+        }
+
+        return result;
+    }
 }
diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
index f23caf2..0fdf2da 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayWindow.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
@@ -30,6 +30,7 @@
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
 import android.view.TextureView;
+import android.view.ThreadedRenderer;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.TextureView.SurfaceTextureListener;
@@ -95,6 +96,8 @@
     public OverlayDisplayWindow(Context context, String name,
             int width, int height, int densityDpi, int gravity, boolean secure,
             Listener listener) {
+        // Workaround device freeze (b/38372997)
+        ThreadedRenderer.disableVsync();
         mContext = context;
         mName = name;
         mGravity = gravity;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f038f9a..5ee7ac4 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -307,11 +307,15 @@
 
     // used as a mutex for access to all active notifications & listeners
     final Object mNotificationLock = new Object();
+    @GuardedBy("mNotificationLock")
     final ArrayList<NotificationRecord> mNotificationList =
             new ArrayList<NotificationRecord>();
+    @GuardedBy("mNotificationLock")
     final ArrayMap<String, NotificationRecord> mNotificationsByKey =
             new ArrayMap<String, NotificationRecord>();
+    @GuardedBy("mNotificationLock")
     final ArrayList<NotificationRecord> mEnqueuedNotifications = new ArrayList<>();
+    @GuardedBy("mNotificationLock")
     final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>();
     final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
     final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();
@@ -607,7 +611,7 @@
         @Override
         public void onPanelRevealed(boolean clearEffects, int items) {
             MetricsLogger.visible(getContext(), MetricsEvent.NOTIFICATION_PANEL);
-            MetricsLogger.histogram(getContext(), "notification_load", items);
+            MetricsLogger.histogram(getContext(), "note_load", items);
             EventLogTags.writeNotificationPanelRevealed(items);
             if (clearEffects) {
                 clearEffects();
@@ -2806,7 +2810,8 @@
             // Clear summary.
             final NotificationRecord removed = findNotificationByKeyLocked(summaries.remove(pkg));
             if (removed != null) {
-                cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED);
+                boolean wasPosted = removeFromNotificationListsLocked(removed);
+                cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED, wasPosted);
             }
         }
     }
@@ -3420,7 +3425,8 @@
                     .setType(MetricsEvent.TYPE_CLOSE)
                     .addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
                             mSnoozeCriterionId == null ? 0 : 1));
-            cancelNotificationLocked(r, false, REASON_SNOOZED);
+            boolean wasPosted = removeFromNotificationListsLocked(r);
+            cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted);
             updateLightsLocked();
             if (mSnoozeCriterionId != null) {
                 mNotificationAssistants.notifyAssistantSnoozedLocked(r.sbn, mSnoozeCriterionId);
@@ -4206,15 +4212,18 @@
         manager.sendAccessibilityEvent(event);
     }
 
+    /**
+     * Removes all NotificationsRecords with the same key as the given notification record
+     * from both lists. Do not call this method while iterating over either list.
+     */
     @GuardedBy("mNotificationLock")
-    private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason) {
-        final String canceledKey = r.getKey();
-
-        // Remove from both lists, either list could have a separate Record for what is effectively
-        // the same notification.
+    private boolean removeFromNotificationListsLocked(NotificationRecord r) {
+        // Remove from both lists, either list could have a separate Record for what is
+        // effectively the same notification.
         boolean wasPosted = false;
         NotificationRecord recordInList = null;
-        if ((recordInList = findNotificationByListLocked(mNotificationList, r.getKey())) != null) {
+        if ((recordInList = findNotificationByListLocked(mNotificationList, r.getKey()))
+                != null) {
             mNotificationList.remove(recordInList);
             mNotificationsByKey.remove(recordInList.sbn.getKey());
             wasPosted = true;
@@ -4223,6 +4232,13 @@
                 != null) {
             mEnqueuedNotifications.remove(recordInList);
         }
+        return wasPosted;
+    }
+
+    @GuardedBy("mNotificationLock")
+    private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason,
+            boolean wasPosted) {
+        final String canceledKey = r.getKey();
 
         // Record caller.
         recordCallerLocked(r);
@@ -4363,7 +4379,8 @@
                         }
 
                         // Cancel the notification.
-                        cancelNotificationLocked(r, sendDelete, reason);
+                        boolean wasPosted = removeFromNotificationListsLocked(r);
+                        cancelNotificationLocked(r, sendDelete, reason, wasPosted);
                         cancelGroupChildrenLocked(r, callingUid, callingPid, listenerName,
                                 sendDelete);
                         updateLightsLocked();
@@ -4440,11 +4457,11 @@
                     cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
                             pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
                             false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
-                            listenerName);
+                            listenerName, true /* wasPosted */);
                     cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
                             callingPid, pkg, true /*nullPkgIndicatesUserSwitch*/, channelId,
                             flagChecker, false /*includeCurrentProfiles*/, userId,
-                            false /*sendDelete*/, reason, listenerName);
+                            false /*sendDelete*/, reason, listenerName, false /* wasPosted */);
                     mSnoozeHelper.cancel(userId, pkg);
                 }
             }
@@ -4460,7 +4477,7 @@
     private void cancelAllNotificationsByListLocked(ArrayList<NotificationRecord> notificationList,
             int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch,
             String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId,
-            boolean sendDelete, int reason, String listenerName) {
+            boolean sendDelete, int reason, String listenerName, boolean wasPosted) {
         ArrayList<NotificationRecord> canceledNotifications = null;
         for (int i = notificationList.size() - 1; i >= 0; --i) {
             NotificationRecord r = notificationList.get(i);
@@ -4488,8 +4505,9 @@
             if (canceledNotifications == null) {
                 canceledNotifications = new ArrayList<>();
             }
+            notificationList.remove(i);
             canceledNotifications.add(r);
-            cancelNotificationLocked(r, sendDelete, reason);
+            cancelNotificationLocked(r, sendDelete, reason, wasPosted);
         }
         if (canceledNotifications != null) {
             final int M = canceledNotifications.size();
@@ -4548,11 +4566,11 @@
                     cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
                             null, false /*nullPkgIndicatesUserSwitch*/, null, flagChecker,
                             includeCurrentProfiles, userId, true /*sendDelete*/, reason,
-                            listenerName);
+                            listenerName, true);
                     cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
                             callingPid, null, false /*nullPkgIndicatesUserSwitch*/, null,
                             flagChecker, includeCurrentProfiles, userId, true /*sendDelete*/,
-                            reason, listenerName);
+                            reason, listenerName, false);
                     mSnoozeHelper.cancel(userId, includeCurrentProfiles);
                 }
             }
@@ -4569,7 +4587,6 @@
         }
 
         String pkg = r.sbn.getPackageName();
-        int userId = r.getUserId();
 
         if (pkg == null) {
             if (DBG) Log.e(TAG, "No package for group summary: " + r.getKey());
@@ -4577,15 +4594,15 @@
         }
 
         cancelGroupChildrenByListLocked(mNotificationList, r, callingUid, callingPid, listenerName,
-                sendDelete);
+                sendDelete, true);
         cancelGroupChildrenByListLocked(mEnqueuedNotifications, r, callingUid, callingPid,
-                listenerName, sendDelete);
+                listenerName, sendDelete, false);
     }
 
     @GuardedBy("mNotificationLock")
     private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
             NotificationRecord parentNotification, int callingUid, int callingPid,
-            String listenerName, boolean sendDelete) {
+            String listenerName, boolean sendDelete, boolean wasPosted) {
         final String pkg = parentNotification.sbn.getPackageName();
         final int userId = parentNotification.getUserId();
         final int reason = REASON_GROUP_SUMMARY_CANCELED;
@@ -4597,7 +4614,8 @@
                     && (childR.getFlags() & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
                 EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
                         childSbn.getTag(), userId, 0, 0, reason, listenerName);
-                cancelNotificationLocked(childR, sendDelete, reason);
+                notificationList.remove(i);
+                cancelNotificationLocked(childR, sendDelete, reason, wasPosted);
             }
         }
     }
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 8952870..6953ffd 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -25,6 +25,7 @@
 import android.app.NotificationChannel;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -170,6 +171,11 @@
     private Uri calculateSound() {
         final Notification n = sbn.getNotification();
 
+        // No notification sounds on tv
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+            return null;
+        }
+
         Uri sound = mChannel.getSound();
         if (mPreChannelsNotification && (getChannel().getUserLockedFields()
                 & NotificationChannel.USER_LOCKED_SOUND) == 0) {
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 2c0cc95..b3c6ff6 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -53,6 +53,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -1186,6 +1187,6 @@
         boolean showBadge = DEFAULT_SHOW_BADGE;
 
         ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
-        ArrayMap<String, NotificationChannelGroup> groups = new ArrayMap<>();
+        Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
    }
 }
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index ef3e7bc..2940a6e 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -714,11 +714,17 @@
 
         final Map<String, List<String>> pendingChanges = new ArrayMap<>(targetPackageNames.size());
         synchronized (mLock) {
+            final List<String> frameworkOverlays =
+                mImpl.getEnabledOverlayPackageNames("android", userId);
             final int N = targetPackageNames.size();
             for (int i = 0; i < N; i++) {
                 final String targetPackageName = targetPackageNames.get(i);
-                pendingChanges.put(targetPackageName,
-                        mImpl.getEnabledOverlayPackageNames(targetPackageName, userId));
+                List<String> list = new ArrayList<>();
+                if (!"android".equals(targetPackageName)) {
+                    list.addAll(frameworkOverlays);
+                }
+                list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId));
+                pendingChanges.put(targetPackageName, list);
             }
         }
 
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 261bcc5..db6e974 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -170,6 +170,7 @@
 
         final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
         updateAllOverlaysForTarget(packageName, userId, targetPackage);
+        mListener.onOverlaysChanged(packageName, userId);
     }
 
     void onTargetPackageChanged(@NonNull final String packageName, final int userId) {
@@ -178,7 +179,9 @@
         }
 
         final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
-        updateAllOverlaysForTarget(packageName, userId, targetPackage);
+        if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) {
+            mListener.onOverlaysChanged(packageName, userId);
+        }
     }
 
     void onTargetPackageUpgrading(@NonNull final String packageName, final int userId) {
@@ -186,7 +189,9 @@
             Slog.d(TAG, "onTargetPackageUpgrading packageName=" + packageName + " userId=" + userId);
         }
 
-        updateAllOverlaysForTarget(packageName, userId, null);
+        if (updateAllOverlaysForTarget(packageName, userId, null)) {
+            mListener.onOverlaysChanged(packageName, userId);
+        }
     }
 
     void onTargetPackageUpgraded(@NonNull final String packageName, final int userId) {
@@ -195,7 +200,9 @@
         }
 
         final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
-        updateAllOverlaysForTarget(packageName, userId, targetPackage);
+        if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) {
+            mListener.onOverlaysChanged(packageName, userId);
+        }
     }
 
     void onTargetPackageRemoved(@NonNull final String packageName, final int userId) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f5808af..5c54ba8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -862,8 +862,15 @@
 
                         mResolvedInstructionSets.add(archSubDir.getName());
                         List<File> oatFiles = Arrays.asList(archSubDir.listFiles());
-                        if (!oatFiles.isEmpty()) {
-                            mResolvedInheritedFiles.addAll(oatFiles);
+
+                        // Only add compiled files associated with the base.
+                        // Once b/62269291 is resolved, we can add all compiled files again.
+                        for (File oatFile : oatFiles) {
+                            if (oatFile.getName().equals("base.art")
+                                    || oatFile.getName().equals("base.odex")
+                                    || oatFile.getName().equals("base.vdex")) {
+                                mResolvedInheritedFiles.add(oatFile);
+                            }
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index bf3e5d3..682d666 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -18164,8 +18164,10 @@
             // step during installation. Instead, we'll take extra time the first time the
             // instant app starts. It's preferred to do it this way to provide continuous
             // progress to the user instead of mysteriously blocking somewhere in the
-            // middle of running an instant app.
-            if (!instantApp) {
+            // middle of running an instant app. The default behaviour can be overridden
+            // via gservices.
+            if (!instantApp || Global.getInt(
+                        mContext.getContentResolver(), Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0) {
                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
                 // Do not run PackageDexOptimizer through the local performDexOpt
                 // method because `pkg` may not be in `mPackages` yet.
@@ -19378,7 +19380,7 @@
         synchronized (mPackages) {
             final PackageSetting ps = mSettings.mPackages.get(packageName);
             if (ps == null || filterAppAccessLPr(ps, Binder.getCallingUid(), userId)) {
-                return true;
+                return false;
             }
             return mSettings.getBlockUninstallLPr(userId, packageName);
         }
@@ -21654,8 +21656,7 @@
         public static final int DUMP_FROZEN = 1 << 19;
         public static final int DUMP_DEXOPT = 1 << 20;
         public static final int DUMP_COMPILER_STATS = 1 << 21;
-        public static final int DUMP_ENABLED_OVERLAYS = 1 << 22;
-        public static final int DUMP_CHANGES = 1 << 23;
+        public static final int DUMP_CHANGES = 1 << 22;
 
         public static final int OPTION_SHOW_FILTERS = 1 << 0;
 
@@ -21899,8 +21900,6 @@
                 dumpState.setDump(DumpState.DUMP_DEXOPT);
             } else if ("compiler-stats".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_COMPILER_STATS);
-            } else if ("enabled-overlays".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_ENABLED_OVERLAYS);
             } else if ("changes".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_CHANGES);
             } else if ("write".equals(cmd)) {
@@ -24680,12 +24679,7 @@
                 }
 
                 final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
-                String[] frameworkOverlayPaths = null;
-                if (!"android".equals(targetPackageName)) {
-                    frameworkOverlayPaths =
-                            mSettings.mPackages.get("android").getOverlayPaths(userId);
-                }
-                ps.setOverlayPaths(overlayPaths, frameworkOverlayPaths, userId);
+                ps.setOverlayPaths(overlayPaths, userId);
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index d17267f..f685127 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -330,21 +330,9 @@
         modifyUserState(userId).installReason = installReason;
     }
 
-    void setOverlayPaths(List<String> overlayPaths, String[] frameworkOverlayPaths, int userId) {
-        if (overlayPaths == null && frameworkOverlayPaths == null) {
-            modifyUserState(userId).overlayPaths = null;
-            return;
-        }
-        final List<String> paths;
-        if (frameworkOverlayPaths == null) {
-            paths = overlayPaths;
-        } else {
-            paths = Lists.newArrayList(frameworkOverlayPaths);
-            if (overlayPaths != null) {
-                paths.addAll(overlayPaths);
-            }
-        }
-        modifyUserState(userId).overlayPaths = paths.toArray(new String[paths.size()]);
+    void setOverlayPaths(List<String> overlayPaths, int userId) {
+        modifyUserState(userId).overlayPaths = overlayPaths == null ? null :
+            overlayPaths.toArray(new String[overlayPaths.size()]);
     }
 
     String[] getOverlayPaths(int userId) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b006c2d..45d0c58 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4861,9 +4861,9 @@
 
             String[] overlayPaths = ps.getOverlayPaths(user.id);
             if (overlayPaths != null && overlayPaths.length > 0) {
-                pw.println("Overlay paths:");
+                pw.print(prefix); pw.println("  overlay paths:");
                 for (String path : overlayPaths) {
-                    pw.println(path);
+                    pw.print(prefix); pw.print("    "); pw.println(path);
                 }
             }
 
diff --git a/services/core/java/com/android/server/policy/BarController.java b/services/core/java/com/android/server/policy/BarController.java
index 7a28081..b179235 100644
--- a/services/core/java/com/android/server/policy/BarController.java
+++ b/services/core/java/com/android/server/policy/BarController.java
@@ -166,8 +166,14 @@
         return change || stateChanged;
     }
 
-    void setOnBarVisibilityChangedListener(OnBarVisibilityChangedListener listener) {
+    void setOnBarVisibilityChangedListener(OnBarVisibilityChangedListener listener,
+            boolean invokeWithState) {
         mVisibilityChangeListener = listener;
+        if (invokeWithState) {
+            // Optionally report the initial window state for initialization purposes
+            mHandler.obtainMessage(MSG_NAV_BAR_VISIBILITY_CHANGED,
+                    (mState == StatusBarManager.WINDOW_STATE_SHOWING) ? 1 : 0, 0).sendToTarget();
+        }
     }
 
     protected boolean skipAnimation() {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 126e3ec..5c6521b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1033,7 +1033,7 @@
             new BarController.OnBarVisibilityChangedListener() {
         @Override
         public void onBarVisibilityChanged(boolean visible) {
-            mAccessibilityManager.notifyAccessibilityButtonAvailabilityChanged(visible);
+            mAccessibilityManager.notifyAccessibilityButtonVisibilityChanged(visible);
         }
     };
 
@@ -3017,7 +3017,7 @@
                 mNavigationBar = win;
                 mNavigationBarController.setWindow(win);
                 mNavigationBarController.setOnBarVisibilityChangedListener(
-                        mNavBarVisibilityListener);
+                        mNavBarVisibilityListener, true);
                 if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
                 break;
             case TYPE_NAVIGATION_BAR_PANEL:
@@ -7713,6 +7713,8 @@
                 return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
             case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
                 return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+            case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
+                return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
             default:
                 return null;
         }
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index b937f9d..55d4719 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -24,9 +24,12 @@
 import android.app.Vr2dDisplayProperties;
 import android.app.NotificationManager;
 import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -39,6 +42,7 @@
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
@@ -140,7 +144,13 @@
     private final NotificationAccessManager mNotifAccessManager = new NotificationAccessManager();
     /** Tracks the state of the screen and keyguard UI.*/
     private int mSystemSleepFlags = FLAG_AWAKE;
+    /**
+     * Set when ACTION_USER_UNLOCKED is fired. We shouldn't try to bind to the
+     * vr service before then.
+     */
+    private boolean mUserUnlocked;
     private Vr2dDisplay mVr2dDisplay;
+    private boolean mBootsToVr;
 
     private static final int MSG_VR_STATE_CHANGE = 0;
     private static final int MSG_PENDING_VR_STATE_CHANGE = 1;
@@ -152,15 +162,20 @@
      * If VR mode is not allowed to be enabled, calls to set VR mode will be cached.  When VR mode
      * is again allowed to be enabled, the most recent cached state will be applied.
      *
-     * @param allowed {@code true} if calling any of the setVrMode methods may cause the device to
-     *   enter VR mode.
      */
-    private void setVrModeAllowedLocked(boolean allowed) {
+    private void updateVrModeAllowedLocked() {
+        boolean allowed = mSystemSleepFlags == FLAG_ALL && mUserUnlocked;
         if (mVrModeAllowed != allowed) {
             mVrModeAllowed = allowed;
             if (DBG) Slog.d(TAG, "VR mode is " + ((allowed) ? "allowed" : "disallowed"));
             if (mVrModeAllowed) {
+                if (mBootsToVr) {
+                    setPersistentVrModeEnabled(true);
+                }
                 consumeAndApplyPendingStateLocked();
+                if (mBootsToVr && !mVrModeEnabled) {
+                  setVrMode(true, mDefaultVrService, 0, null);
+                }
             } else {
                 // Disable persistent mode when VR mode isn't allowed, allows an escape hatch to
                 // exit persistent VR mode when screen is turned off.
@@ -187,7 +202,7 @@
                 mSystemSleepFlags &= ~FLAG_AWAKE;
             }
 
-            setVrModeAllowedLocked(mSystemSleepFlags == FLAG_ALL);
+            updateVrModeAllowedLocked();
         }
     }
 
@@ -198,7 +213,14 @@
             } else {
                 mSystemSleepFlags &= ~FLAG_SCREEN_ON;
             }
-            setVrModeAllowedLocked(mSystemSleepFlags == FLAG_ALL);
+            updateVrModeAllowedLocked();
+        }
+    }
+
+    private void setUserUnlocked() {
+        synchronized(mLock) {
+            mUserUnlocked = true;
+            updateVrModeAllowedLocked();
         }
     }
 
@@ -563,6 +585,7 @@
             mContext = getContext();
         }
 
+        mBootsToVr = SystemProperties.getBoolean("ro.boot.vr", false);
         publishLocalService(VrManagerInternal.class, new LocalService());
         publishBinderService(Context.VR_SERVICE, mVrManager.asBinder());
     }
@@ -597,10 +620,17 @@
             ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
             mVr2dDisplay = new Vr2dDisplay(dm, ami, mVrManager);
             mVr2dDisplay.init(getContext());
-        } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
-            synchronized (mLock) {
-                mVrModeAllowed = true;
-            }
+
+            IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+            getContext().registerReceiver(new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+                            VrManagerService.this.setUserUnlocked();
+                        }
+                    }
+                }, intentFilter);
         }
     }
 
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 71c92b8..929f28d 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -52,7 +52,6 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
-import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -92,7 +91,6 @@
 
 import com.android.internal.R;
 import com.android.internal.content.PackageMonitor;
-import com.android.internal.graphics.palette.Palette;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
@@ -168,7 +166,6 @@
     static final String WALLPAPER_LOCK_ORIG = "wallpaper_lock_orig";
     static final String WALLPAPER_LOCK_CROP = "wallpaper_lock";
     static final String WALLPAPER_INFO = "wallpaper_info.xml";
-    static final int MAX_WALLPAPER_EXTRACTION_AREA = 112 * 112;
 
     // All the various per-user state files we need to be aware of
     static final String[] sPerUserFiles = new String[] {
@@ -397,8 +394,9 @@
         // It prevents color extraction on big bitmaps.
         int wallpaperId = -1;
 
+        boolean imageWallpaper = false;
         synchronized (mLock) {
-            final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent)
+            imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent)
                     || wallpaper.wallpaperComponent == null;
             if (imageWallpaper) {
                 if (wallpaper.cropFile != null && wallpaper.cropFile.exists()) {
@@ -422,45 +420,33 @@
             wallpaperId = wallpaper.wallpaperId;
         }
 
-        Bitmap bitmap = null;
+        WallpaperColors colors = null;
         if (cropFile != null) {
-            bitmap = BitmapFactory.decodeFile(cropFile);
+            Bitmap bitmap = BitmapFactory.decodeFile(cropFile);
+            colors = WallpaperColors.fromBitmap(bitmap);
+            bitmap.recycle();
         } else if (thumbnail != null) {
-            // Calculate how big the bitmap needs to be.
-            // This avoids unnecessary processing and allocation inside Palette.
-            final int requestedArea = thumbnail.getIntrinsicWidth() *
-                    thumbnail.getIntrinsicHeight();
-            double scale = 1;
-            if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) {
-                scale = Math.sqrt(MAX_WALLPAPER_EXTRACTION_AREA / (double) requestedArea);
-            }
-            bitmap = Bitmap.createBitmap((int) (thumbnail.getIntrinsicWidth() * scale),
-                    (int) (thumbnail.getIntrinsicHeight() * scale), Bitmap.Config.ARGB_8888);
-            final Canvas bmpCanvas = new Canvas(bitmap);
-            thumbnail.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
-            thumbnail.draw(bmpCanvas);
+            colors = WallpaperColors.fromDrawable(thumbnail);
         }
 
-        if (bitmap == null) {
+        if (colors == null) {
             Slog.w(TAG, "Cannot extract colors because wallpaper could not be read.");
             return;
         }
 
-        Palette palette = Palette
-                .from(bitmap)
-                .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
-                .generate();
-        bitmap.recycle();
-
-        final List<Pair<Color, Integer>> colors = new ArrayList<>();
-        for (Palette.Swatch swatch : palette.getSwatches()) {
-            colors.add(new Pair<>(Color.valueOf(swatch.getRgb()),
-                    swatch.getPopulation()));
+        // Even though we can extract colors from live wallpaper thumbnails,
+        // it's risky to assume that it might support dark text on top of it:
+        //    • Thumbnail might not be accurate.
+        //    • Colors might change over time.
+        if (!imageWallpaper) {
+            int colorHints = colors.getColorHints();
+            colorHints &= ~WallpaperColors.HINT_SUPPORTS_DARK_TEXT;
+            colors.setColorHints(colorHints);
         }
 
         synchronized (mLock) {
             if (wallpaper.wallpaperId == wallpaperId) {
-                wallpaper.primaryColors = new WallpaperColors(colors);
+                wallpaper.primaryColors = colors;
             } else {
                 Slog.w(TAG, "Not setting primary colors since wallpaper changed");
             }
@@ -2224,17 +2210,16 @@
         }
 
         if (wallpaper.primaryColors != null) {
-            int colorsCount = wallpaper.primaryColors.getColors().size();
+            int colorsCount = wallpaper.primaryColors.getMainColors().size();
             out.attribute(null, "colorsCount", Integer.toString(colorsCount));
             if (colorsCount > 0) {
                 for (int i = 0; i < colorsCount; i++) {
-                    Pair<Color, Integer> wc = wallpaper.primaryColors.getColors().get(i);
-                    out.attribute(null, "colorValue"+i, Integer.toString(wc.first.toArgb()));
-                    out.attribute(null, "colorWeight"+i, Integer.toString(wc.second));
+                    final Color wc = wallpaper.primaryColors.getMainColors().get(i);
+                    out.attribute(null, "colorValue"+i, Integer.toString(wc.toArgb()));
                 }
             }
             out.attribute(null, "supportsDarkText",
-                    Integer.toString(wallpaper.primaryColors.supportsDarkText() ? 1 : 0));
+                    Integer.toString(wallpaper.primaryColors.getColorHints()));
         }
 
         out.attribute(null, "name", wallpaper.name);
@@ -2469,15 +2454,22 @@
         wallpaper.padding.bottom = getAttributeInt(parser, "paddingBottom", 0);
         int colorsCount = getAttributeInt(parser, "colorsCount", 0);
         if (colorsCount > 0) {
-            List<Pair<Color, Integer>> colors = new ArrayList<>();
+            Color primary = null, secondary = null, tertiary = null;
+            final List<Color> colors = new ArrayList<>();
             for (int i = 0; i < colorsCount; i++) {
-                colors.add(new Pair<>(
-                    Color.valueOf(getAttributeInt(parser, "colorValue"+i, 0)),
-                    getAttributeInt(parser, "colorWeight"+i, 0)
-                ));
+                Color color = Color.valueOf(getAttributeInt(parser, "colorValue" + i, 0));
+                if (i == 0) {
+                    primary = color;
+                } else if (i == 1) {
+                    secondary = color;
+                } else if (i == 2) {
+                    tertiary = color;
+                } else {
+                    break;
+                }
             }
-            boolean dark = getAttributeInt(parser, "supportsDarkText", 0) == 1;
-            wallpaper.primaryColors = new WallpaperColors(colors, dark);
+            int colorHints = getAttributeInt(parser, "colorHints", 0);
+            wallpaper.primaryColors = new WallpaperColors(primary, secondary, tertiary, colorHints);
         }
         wallpaper.name = parser.getAttributeValue(null, "name");
         wallpaper.allowBackup = "true".equals(parser.getAttributeValue(null, "backup"));
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index f138add..78f2195 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT;
+
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -418,13 +421,7 @@
         public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
             MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
             if (spec != null && !spec.isNop()) {
-                WindowManagerPolicy policy = mWindowManagerService.mPolicy;
-                final int windowType = windowState.mAttrs.type;
-                if (!policy.isTopLevelWindow(windowType) && windowState.isChildWindow()
-                        && !policy.canMagnifyWindow(windowType)) {
-                    return null;
-                }
-                if (!policy.canMagnifyWindow(windowState.mAttrs.type)) {
+                if (!mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
                     return null;
                 }
             }
@@ -540,8 +537,9 @@
                 final int visibleWindowCount = visibleWindows.size();
                 for (int i = visibleWindowCount - 1; i >= 0; i--) {
                     WindowState windowState = visibleWindows.valueAt(i);
-                    if (windowState.mAttrs.type == WindowManager
-                            .LayoutParams.TYPE_MAGNIFICATION_OVERLAY) {
+                    if ((windowState.mAttrs.type == TYPE_MAGNIFICATION_OVERLAY)
+                            || ((windowState.mAttrs.privateFlags
+                            & PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT) != 0)) {
                         continue;
                     }
 
@@ -715,7 +713,7 @@
                     mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay()
                             .getLayerStack());
                     mSurfaceControl.setLayer(mWindowManagerService.mPolicy.getWindowLayerFromTypeLw(
-                            WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY)
+                            TYPE_MAGNIFICATION_OVERLAY)
                             * WindowManagerService.TYPE_LAYER_MULTIPLIER);
                     mSurfaceControl.setPosition(0, 0);
                     mSurface.copyFrom(mSurfaceControl);
@@ -1313,7 +1311,7 @@
                     && windowType != WindowManager.LayoutParams.TYPE_DRAG
                     && windowType != WindowManager.LayoutParams.TYPE_INPUT_CONSUMER
                     && windowType != WindowManager.LayoutParams.TYPE_POINTER
-                    && windowType != WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY
+                    && windowType != TYPE_MAGNIFICATION_OVERLAY
                     && windowType != WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY
                     && windowType != WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY
                     && windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index c1c72ca..b1ed358 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -2044,7 +2044,10 @@
         if (forceOverride || isKeyguardTransit(transit) || !isTransitionSet()
                 || mNextAppTransition == TRANSIT_NONE) {
             setAppTransition(transit, flags);
-        } else if (!alwaysKeepCurrent) {
+        }
+        // We never want to change from a Keyguard transit to a non-Keyguard transit, as our logic
+        // relies on the fact that we always execute a Keyguard transition after preparing one.
+        else if (!alwaysKeepCurrent && !isKeyguardTransit(transit)) {
             if (transit == TRANSIT_TASK_OPEN && isTransitionEqual(TRANSIT_TASK_CLOSE)) {
                 // Opening a new task always supersedes a close for the anim.
                 setAppTransition(transit, flags);
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 8cfbf68..fe74947 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -38,8 +38,11 @@
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.Trace;
 import android.util.Slog;
+import android.view.DisplayInfo;
 import android.view.IApplicationToken;
 import android.view.WindowManagerPolicy.StartingSurface;
 
@@ -61,23 +64,38 @@
     private final IApplicationToken mToken;
     private final Handler mHandler;
 
-    private final Runnable mOnStartingWindowDrawn = () -> {
-        if (mListener == null) {
-            return;
-        }
-        if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
-                + AppWindowContainerController.this.mToken);
-        mListener.onStartingWindowDrawn();
-    };
+    private final class H extends Handler {
+        public static final int NOTIFY_WINDOWS_DRAWN = 1;
+        public static final int NOTIFY_STARTING_WINDOW_DRAWN = 2;
 
-    private final Runnable mOnWindowsDrawn = () -> {
-        if (mListener == null) {
-            return;
+        public H(Looper looper) {
+            super(looper);
         }
-        if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
-                + AppWindowContainerController.this.mToken);
-        mListener.onWindowsDrawn();
-    };
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case NOTIFY_WINDOWS_DRAWN:
+                    if (mListener == null) {
+                        return;
+                    }
+                    if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
+                            + AppWindowContainerController.this.mToken);
+                    mListener.onWindowsDrawn(msg.getWhen());
+                    break;
+                case NOTIFY_STARTING_WINDOW_DRAWN:
+                    if (mListener == null) {
+                        return;
+                    }
+                    if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
+                            + AppWindowContainerController.this.mToken);
+                    mListener.onStartingWindowDrawn(msg.getWhen());
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
 
     private final Runnable mOnWindowsVisible = () -> {
         if (mListener == null) {
@@ -212,7 +230,7 @@
             int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
             WindowManagerService service, Configuration overrideConfig, Rect bounds) {
         super(listener, service);
-        mHandler = new Handler(service.mH.getLooper());
+        mHandler = new H(service.mH.getLooper());
         mToken = token;
         synchronized(mWindowMap) {
             AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder());
@@ -481,7 +499,7 @@
     public boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
             CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
             IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
-            boolean allowTaskSnapshot, boolean activityCreated) {
+            boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents) {
         synchronized(mWindowMap) {
             if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "setAppStartingWindow: token=" + mToken
                     + " pkg=" + pkg + " transferFrom=" + transferFrom + " newTask=" + newTask
@@ -510,11 +528,14 @@
                 return false;
             }
 
+            final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
+                    mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
+                    false /* restoreFromDisk */, false /* reducedResolution */);
             final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
-                    allowTaskSnapshot, activityCreated);
+                    allowTaskSnapshot, activityCreated, fromRecents, snapshot);
 
             if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
-                return createSnapshot();
+                return createSnapshot(snapshot);
             }
 
             // If this is a translucent window, then don't show a starting window -- the current
@@ -582,7 +603,8 @@
     }
 
     private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning,
-            boolean allowTaskSnapshot, boolean activityCreated) {
+            boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents,
+            TaskSnapshot snapshot) {
         if (mService.mAppTransition.getAppTransition() == TRANSIT_DOCK_TASK_FROM_RECENTS) {
             // TODO(b/34099271): Remove this statement to add back the starting window and figure
             // out why it causes flickering, the starting window appears over the thumbnail while
@@ -591,7 +613,9 @@
         } else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
             return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
         } else if (taskSwitch && allowTaskSnapshot) {
-            return STARTING_WINDOW_TYPE_SNAPSHOT;
+            return snapshot == null ? STARTING_WINDOW_TYPE_NONE
+                    : snapshotFillsWidth(snapshot) || fromRecents ? STARTING_WINDOW_TYPE_SNAPSHOT
+                    : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
         } else {
             return STARTING_WINDOW_TYPE_NONE;
         }
@@ -605,11 +629,7 @@
         mService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
     }
 
-    private boolean createSnapshot() {
-        final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
-                mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
-                false /* restoreFromDisk */, false /* reducedResolution */);
-
+    private boolean createSnapshot(TaskSnapshot snapshot) {
         if (snapshot == null) {
             return false;
         }
@@ -620,6 +640,24 @@
         return true;
     }
 
+    private boolean snapshotFillsWidth(TaskSnapshot snapshot) {
+        if (snapshot == null) {
+            return false;
+        }
+        final Rect rect = new Rect(0, 0, snapshot.getSnapshot().getWidth(),
+                snapshot.getSnapshot().getHeight());
+        rect.inset(snapshot.getContentInsets());
+        final Rect taskBoundsWithoutInsets = new Rect();
+        mContainer.getTask().getBounds(taskBoundsWithoutInsets);
+        final DisplayInfo di = mContainer.getDisplayContent().getDisplayInfo();
+        final Rect displayBounds = new Rect(0, 0, di.logicalWidth, di.logicalHeight);
+        final Rect stableInsets = new Rect();
+        mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+                stableInsets);
+        displayBounds.inset(stableInsets);
+        return rect.width() >= displayBounds.width();
+    }
+
     public void removeStartingWindow() {
         synchronized (mWindowMap) {
             if (mHandler.hasCallbacks(mRemoveStartingWindow)) {
@@ -740,11 +778,11 @@
     }
 
     void reportStartingWindowDrawn() {
-        mHandler.post(mOnStartingWindowDrawn);
+        mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_STARTING_WINDOW_DRAWN));
     }
 
     void reportWindowsDrawn() {
-        mHandler.post(mOnWindowsDrawn);
+        mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_WINDOWS_DRAWN));
     }
 
     void reportWindowsVisible() {
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerListener.java b/services/core/java/com/android/server/wm/AppWindowContainerListener.java
index 26537f2..8a39a74 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerListener.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerListener.java
@@ -19,7 +19,7 @@
 /** Interface used by the creator of the controller to listen to changes with the container. */
 public interface AppWindowContainerListener extends WindowContainerListener {
     /** Called when the windows associated app window container are drawn. */
-    void onWindowsDrawn();
+    void onWindowsDrawn(long timestamp);
     /** Called when the windows associated app window container are visible. */
     void onWindowsVisible();
     /** Called when the windows associated app window container are no longer visible. */
@@ -28,7 +28,7 @@
     /**
      * Called when the starting window for this container is drawn.
      */
-    void onStartingWindowDrawn();
+    void onStartingWindowDrawn(long timestamp);
 
     /**
      * Called when the key dispatching to a window associated with the app window container
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 71ecaf6..bd37934 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -394,6 +394,10 @@
                     startingWindow.mPolicyVisibility = false;
                     startingWindow.mPolicyVisibilityAfterAnim = false;
                 }
+
+                // We are becoming visible, so better freeze the screen with the windows that are
+                // getting visible so we also wait for them.
+                forAllWindows(mService::makeWindowFreezingScreenIfNeededLocked, true);
             }
 
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "setVisibility: " + this
@@ -891,8 +895,24 @@
         return mPendingRelaunchCount > 0;
     }
 
+    boolean shouldFreezeBounds() {
+        final Task task = getTask();
+
+        // For freeform windows, we can't freeze the bounds at the moment because this would make
+        // the resizing unresponsive.
+        if (task == null || task.inFreeformWorkspace()) {
+            return false;
+        }
+
+        // We freeze the bounds while drag resizing to deal with the time between
+        // the divider/drag handle being released, and the handling it's new
+        // configuration. If we are relaunched outside of the drag resizing state,
+        // we need to be careful not to do this.
+        return getTask().isDragResizing();
+    }
+
     void startRelaunching() {
-        if (canFreezeBounds()) {
+        if (shouldFreezeBounds()) {
             freezeBounds();
         }
 
@@ -909,9 +929,8 @@
     }
 
     void finishRelaunching() {
-        if (canFreezeBounds()) {
-            unfreezeBounds();
-        }
+        unfreezeBounds();
+
         if (mPendingRelaunchCount > 0) {
             mPendingRelaunchCount--;
         } else {
@@ -926,9 +945,7 @@
         if (mPendingRelaunchCount == 0) {
             return;
         }
-        if (canFreezeBounds()) {
-            unfreezeBounds();
-        }
+        unfreezeBounds();
         mPendingRelaunchCount = 0;
     }
 
@@ -1032,14 +1049,6 @@
         }
     }
 
-    private boolean canFreezeBounds() {
-        final Task task = getTask();
-
-        // For freeform windows, we can't freeze the bounds at the moment because this would make
-        // the resizing unresponsive.
-        return task != null && !task.inFreeformWorkspace();
-    }
-
     /**
      * Freezes the task bounds. The size of this task reported the app will be fixed to the bounds
      * freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even
@@ -1064,9 +1073,10 @@
      * Unfreezes the previously frozen bounds. See {@link #freezeBounds}.
      */
     private void unfreezeBounds() {
-        if (!mFrozenBounds.isEmpty()) {
-            mFrozenBounds.remove();
+        if (mFrozenBounds.isEmpty()) {
+            return;
         }
+        mFrozenBounds.remove();
         if (!mFrozenMergedConfig.isEmpty()) {
             mFrozenMergedConfig.remove();
         }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 54983c8..fbe6f94 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -210,6 +210,7 @@
     final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics();
     /** @see #computeCompatSmallestWidth(boolean, int, int, int, int) */
     private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics();
+
     /**
      * Compat metrics computed based on {@link #mDisplayMetrics}.
      * @see #updateDisplayAndOrientation(int)
@@ -226,6 +227,7 @@
      * @see #updateRotationUnchecked(boolean)
      */
     private int mRotation = 0;
+
     /**
      * Last applied orientation of the display.
      * Constants as per {@link android.content.pm.ActivityInfo.ScreenOrientation}.
@@ -233,6 +235,7 @@
      * @see WindowManagerService#updateOrientationFromAppTokensLocked(boolean, int)
      */
     private int mLastOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
     /**
      * Flag indicating that the application is receiving an orientation that has different metrics
      * than it expected. E.g. Portrait instead of Landscape.
@@ -240,6 +243,7 @@
      * @see #updateRotationUnchecked(boolean)
      */
     private boolean mAltOrientation = false;
+
     /**
      * Orientation forced by some window. If there is no visible window that specifies orientation
      * it is set to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
@@ -247,6 +251,7 @@
      * @see NonAppWindowContainers#getOrientation()
      */
     private int mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
     /**
      * Last orientation forced by the keyguard. It is applied when keyguard is shown and is not
      * occluded.
@@ -255,6 +260,11 @@
      */
     private int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
 
+    /**
+     * Keep track of wallpaper visibility to notify changes.
+     */
+    private boolean mLastWallpaperVisible = false;
+
     private Rect mBaseDisplayRect = new Rect();
     private Rect mContentRect = new Rect();
 
@@ -2765,6 +2775,12 @@
 
         stopDimmingIfNeeded();
 
+        final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
+        if (wallpaperVisible != mLastWallpaperVisible) {
+            mLastWallpaperVisible = wallpaperVisible;
+            mService.mWallpaperVisibilityListeners.notifyWallpaperVisibilityChanged(this);
+        }
+
         while (!mTmpUpdateAllDrawn.isEmpty()) {
             final AppWindowToken atoken = mTmpUpdateAllDrawn.removeLast();
             // See if any windows have been drawn, so they (and others associated with them)
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 9a9e29a..6d33ce2 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -155,6 +155,10 @@
         mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
         mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
         reloadResources();
+        // Initialize the aspect ratio to the default aspect ratio.  Don't do this in reload
+        // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
+        // triggers a configuration change and the resources to be reloaded.
+        mAspectRatio = mDefaultAspectRatio;
     }
 
     void onConfigurationChanged() {
@@ -171,7 +175,6 @@
         mCurrentMinSize = mDefaultMinSize;
         mDefaultAspectRatio = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
-        mAspectRatio = mDefaultAspectRatio;
         final String screenEdgeInsetsDpString = res.getString(
                 com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets);
         final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 551e3bf..a96d224 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -125,6 +125,7 @@
     private final Paint mBackgroundPaint = new Paint();
     private final int mStatusBarColor;
     @VisibleForTesting final SystemBarBackgroundPainter mSystemBarBackgroundPainter;
+    private final int mOrientationOnCreation;
 
     static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token,
             TaskSnapshot snapshot) {
@@ -146,6 +147,7 @@
         final int sysUiVis;
         final int windowFlags;
         final int windowPrivateFlags;
+        final int currentOrientation;
         synchronized (service.mWindowMap) {
             final WindowState mainWindow = token.findMainWindow();
             if (mainWindow == null) {
@@ -183,6 +185,7 @@
             } else {
                 taskBounds = null;
             }
+            currentOrientation = mainWindow.getConfiguration().orientation;
         }
         try {
             final int res = session.addToDisplay(window, window.mSeq, layoutParams,
@@ -197,7 +200,8 @@
         }
         final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
                 surface, snapshot, layoutParams.getTitle(), backgroundColor, statusBarColor,
-                navigationBarColor, sysUiVis, windowFlags, windowPrivateFlags, taskBounds);
+                navigationBarColor, sysUiVis, windowFlags, windowPrivateFlags, taskBounds,
+                currentOrientation);
         window.setOuter(snapshotSurface);
         try {
             session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame,
@@ -215,7 +219,7 @@
     TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface,
             TaskSnapshot snapshot, CharSequence title, int backgroundColor, int statusBarColor,
             int navigationBarColor, int sysUiVis, int windowFlags, int windowPrivateFlags,
-            Rect taskBounds) {
+            Rect taskBounds, int currentOrientation) {
         mService = service;
         mHandler = new Handler(mService.mH.getLooper());
         mSession = WindowManagerGlobal.getWindowSession();
@@ -228,6 +232,7 @@
         mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
                 windowPrivateFlags, sysUiVis, statusBarColor, navigationBarColor);
         mStatusBarColor = statusBarColor;
+        mOrientationOnCreation = currentOrientation;
     }
 
     @Override
@@ -394,6 +399,7 @@
     static class Window extends BaseIWindow {
 
         private TaskSnapshotSurface mOuter;
+
         public void setOuter(TaskSnapshotSurface outer) {
             mOuter = outer;
         }
@@ -403,6 +409,15 @@
                 Rect stableInsets, Rect outsets, boolean reportDraw,
                 MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout,
                 boolean alwaysConsumeNavBar, int displayId) {
+            if (mergedConfiguration != null && mOuter != null
+                    && mOuter.mOrientationOnCreation
+                            != mergedConfiguration.getMergedConfiguration().orientation) {
+
+                // The orientation of the screen is changing. We better remove the snapshot ASAP as
+                // we are going to wait on the new window in any case to unfreeze the screen, and
+                // the starting window is not needed anymore.
+                sHandler.post(mOuter::remove);
+            }
             if (reportDraw) {
                 sHandler.obtainMessage(MSG_REPORT_DRAW, mOuter).sendToTarget();
             }
diff --git a/services/core/java/com/android/server/wm/WallpaperVisibilityListeners.java b/services/core/java/com/android/server/wm/WallpaperVisibilityListeners.java
new file mode 100644
index 0000000..2c06851
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WallpaperVisibilityListeners.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.SparseArray;
+import android.view.IWallpaperVisibilityListener;
+
+/**
+ * Manages and trigger wallpaper visibility listeners.
+ */
+class WallpaperVisibilityListeners {
+
+    /**
+     * A map of displayIds and its listeners.
+     */
+    private final SparseArray<RemoteCallbackList<IWallpaperVisibilityListener>> mDisplayListeners =
+            new SparseArray<>();
+
+    void registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+            int displayId) {
+        RemoteCallbackList<IWallpaperVisibilityListener> listeners =
+                mDisplayListeners.get(displayId);
+        if (listeners == null) {
+            listeners = new RemoteCallbackList<>();
+            mDisplayListeners.append(displayId, listeners);
+        }
+        listeners.register(listener);
+    }
+
+    void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+            int displayId) {
+        RemoteCallbackList<IWallpaperVisibilityListener> listeners =
+                mDisplayListeners.get(displayId);
+        if (listeners == null) {
+            return;
+        }
+        listeners.unregister(listener);
+    }
+
+    void notifyWallpaperVisibilityChanged(DisplayContent displayContent) {
+        final int displayId = displayContent.getDisplayId();
+        final boolean visible = displayContent.mWallpaperController.isWallpaperVisible();
+        RemoteCallbackList<IWallpaperVisibilityListener> displayListeners =
+                mDisplayListeners.get(displayId);
+
+        // No listeners for this display.
+        if (displayListeners == null) {
+            return;
+        }
+
+        int i = displayListeners.beginBroadcast();
+        while (i > 0) {
+            i--;
+            IWallpaperVisibilityListener listener = displayListeners.getBroadcastItem(i);
+            try {
+                listener.onWallpaperVisibilityChanged(visible, displayId);
+            } catch (RemoteException e) {
+                // Nothing to do in here, RemoteCallbackListener will clean it up.
+            }
+        }
+        displayListeners.finishBroadcast();
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index aabf2be..fe5b7f2 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -228,7 +228,10 @@
             Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
         } finally {
             if (transactionOpen) {
-                mService.closeSurfaceTransaction();
+
+                // Do not hold window manager lock while closing the transaction, as this might be
+                // blocking until the next frame, which can lead to total lock starvation.
+                mService.closeSurfaceTransaction(false /* withLockHeld */);
                 if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 41ddf0c..f86534b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -181,6 +181,7 @@
 import android.view.IOnKeyguardExitResult;
 import android.view.IPinnedStackListener;
 import android.view.IRotationWatcher;
+import android.view.IWallpaperVisibilityListener;
 import android.view.IWindow;
 import android.view.IWindowId;
 import android.view.IWindowManager;
@@ -549,9 +550,9 @@
     }
 
     class RotationWatcher {
-        IRotationWatcher mWatcher;
-        IBinder.DeathRecipient mDeathRecipient;
-        int mDisplayId;
+        final IRotationWatcher mWatcher;
+        final IBinder.DeathRecipient mDeathRecipient;
+        final int mDisplayId;
         RotationWatcher(IRotationWatcher watcher, IBinder.DeathRecipient deathRecipient,
                 int displayId) {
             mWatcher = watcher;
@@ -562,6 +563,8 @@
 
     ArrayList<RotationWatcher> mRotationWatchers = new ArrayList<>();
     int mDeferredRotationPauseCount;
+    final WallpaperVisibilityListeners mWallpaperVisibilityListeners =
+            new WallpaperVisibilityListeners();
 
     int mSystemDecorLayer = 0;
     final Rect mScreenRect = new Rect();
@@ -893,10 +896,26 @@
     }
 
     void closeSurfaceTransaction() {
+        closeSurfaceTransaction(true /* withLockHeld */);
+    }
+
+    /**
+     * Closes a surface transaction.
+     *
+     * @param withLockHeld Whether to acquire the window manager while doing so. In some cases
+     *                     holding the lock my lead to starvation in WM in case closeTransaction
+     *                     blocks and we call it repeatedly, like we do for animations.
+     */
+    void closeSurfaceTransaction(boolean withLockHeld) {
         synchronized (mWindowMap) {
             if (mRoot.mSurfaceTraceEnabled) {
                 mRoot.mRemoteEventTrace.closeSurfaceTransaction();
             }
+            if (withLockHeld) {
+                SurfaceControl.closeTransaction();
+            }
+        }
+        if (!withLockHeld) {
             SurfaceControl.closeTransaction();
         }
     }
@@ -1270,14 +1289,6 @@
                           + token + ".  Aborting.");
                     return WindowManagerGlobal.ADD_APP_EXITING;
                 }
-                if (rootType == TYPE_APPLICATION_STARTING
-                        && (attrs.privateFlags & PRIVATE_FLAG_TASK_SNAPSHOT) == 0
-                        && atoken.firstWindowDrawn) {
-                    // No need for this guy!
-                    if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v(
-                            TAG_WM, "**** NO NEED TO START: " + attrs.getTitle());
-                    return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
-                }
             } else if (rootType == TYPE_INPUT_METHOD) {
                 if (token.windowType != TYPE_INPUT_METHOD) {
                     Slog.w(TAG_WM, "Attempted to add input method window with bad token "
@@ -3937,6 +3948,29 @@
         }
     }
 
+    @Override
+    public boolean registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+            int displayId) {
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent == null) {
+                throw new IllegalArgumentException("Trying to register visibility event "
+                        + "for invalid display: " + displayId);
+            }
+            mWallpaperVisibilityListeners.registerWallpaperVisibilityListener(listener, displayId);
+            return displayContent.mWallpaperController.isWallpaperVisible();
+        }
+    }
+
+    @Override
+    public void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+            int displayId) {
+        synchronized (mWindowMap) {
+            mWallpaperVisibilityListeners
+                    .unregisterWallpaperVisibilityListener(listener, displayId);
+        }
+    }
+
     /**
      * Apps that use the compact menu panel (as controlled by the panelMenuIsCompact
      * theme attribute) on devices that feature a physical options menu key attempt to position
@@ -5150,7 +5184,8 @@
                 }
                 break;
                 case NOTIFY_APP_TRANSITION_STARTING: {
-                    mAmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj);
+                    mAmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj,
+                            msg.getWhen());
                 }
                 break;
                 case NOTIFY_APP_TRANSITION_CANCELLED: {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 73f8d27..33cb908 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1524,7 +1524,10 @@
     void prepareSurfaceLocked(final boolean recoveringMemory) {
         final WindowState w = mWin;
         if (!hasSurface()) {
-            if (w.mOrientationChanging) {
+
+            // There is no need to wait for an animation change if our window is gone for layout
+            // already as we'll never be visible.
+            if (w.mOrientationChanging && w.isGoneForLayoutLw()) {
                 if (DEBUG_ORIENTATION) {
                     Slog.v(TAG, "Orientation change skips hidden " + w);
                 }
@@ -1557,13 +1560,11 @@
             hide("prepareSurfaceLocked");
             mWallpaperControllerLocked.hideWallpapers(w);
 
-            // If we are waiting for this window to handle an
-            // orientation change, well, it is hidden, so
-            // doesn't really matter.  Note that this does
-            // introduce a potential glitch if the window
-            // becomes unhidden before it has drawn for the
-            // new orientation.
-            if (w.mOrientationChanging) {
+            // If we are waiting for this window to handle an orientation change. If this window is
+            // really hidden (gone for layout), there is no point in still waiting for it.
+            // Note that this does introduce a potential glitch if the window becomes unhidden
+            // before it has drawn for the new orientation.
+            if (w.mOrientationChanging && w.isGoneForLayoutLw()) {
                 w.mOrientationChanging = false;
                 if (DEBUG_ORIENTATION) Slog.v(TAG,
                         "Orientation change skips hidden " + w);
@@ -1630,18 +1631,19 @@
             displayed = true;
         }
 
-        if (displayed) {
-            if (w.mOrientationChanging) {
-                if (!w.isDrawnLw()) {
-                    mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;
-                    mAnimator.mLastWindowFreezeSource = w;
-                    if (DEBUG_ORIENTATION) Slog.v(TAG,
-                            "Orientation continue waiting for draw in " + w);
-                } else {
-                    w.mOrientationChanging = false;
-                    if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w);
-                }
+        if (w.mOrientationChanging) {
+            if (!w.isDrawnLw()) {
+                mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;
+                mAnimator.mLastWindowFreezeSource = w;
+                if (DEBUG_ORIENTATION) Slog.v(TAG,
+                        "Orientation continue waiting for draw in " + w);
+            } else {
+                w.mOrientationChanging = false;
+                if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w);
             }
+        }
+
+        if (displayed) {
             w.mToken.hasVisible = true;
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 4442bb8..82c862f 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -25,6 +25,7 @@
 import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_INTRA_CLOSE;
 import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN;
 import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_OPEN;
+import static com.android.server.wm.AppTransition.isKeyguardGoingAwayTransit;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -239,7 +240,7 @@
 
         if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
         int transit = mService.mAppTransition.getAppTransition();
-        if (mService.mSkipAppTransitionAnimation) {
+        if (mService.mSkipAppTransitionAnimation && !isKeyguardGoingAwayTransit(transit)) {
             transit = AppTransition.TRANSIT_UNSET;
         }
         mService.mSkipAppTransitionAnimation = false;
@@ -598,42 +599,47 @@
                         + ", openingApps=" + openingApps
                         + ", closingApps=" + closingApps);
         mService.mAnimateWallpaperWithTarget = false;
-        if (closingAppHasWallpaper && openingAppHasWallpaper) {
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!");
-            switch (transit) {
-                case TRANSIT_ACTIVITY_OPEN:
-                case TRANSIT_TASK_OPEN:
-                case TRANSIT_TASK_TO_FRONT:
-                    transit = TRANSIT_WALLPAPER_INTRA_OPEN;
-                    break;
-                case TRANSIT_ACTIVITY_CLOSE:
-                case TRANSIT_TASK_CLOSE:
-                case TRANSIT_TASK_TO_BACK:
-                    transit = TRANSIT_WALLPAPER_INTRA_CLOSE;
-                    break;
-            }
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
-                    "New transit: " + AppTransition.appTransitionToString(transit));
-        } else if (openingCanBeWallpaperTarget && transit == TRANSIT_KEYGUARD_GOING_AWAY) {
+        if (openingCanBeWallpaperTarget && transit == TRANSIT_KEYGUARD_GOING_AWAY) {
             transit = TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                     "New transit: " + AppTransition.appTransitionToString(transit));
-        } else if (oldWallpaper != null && !mService.mOpeningApps.isEmpty()
-                && !openingApps.contains(oldWallpaper.mAppToken)
-                && closingApps.contains(oldWallpaper.mAppToken)) {
-            // We are transitioning from an activity with a wallpaper to one without.
-            transit = TRANSIT_WALLPAPER_CLOSE;
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit away from wallpaper: "
-                    + AppTransition.appTransitionToString(transit));
-        } else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw() &&
-                openingApps.contains(wallpaperTarget.mAppToken)) {
-            // We are transitioning from an activity without
-            // a wallpaper to now showing the wallpaper
-            transit = TRANSIT_WALLPAPER_OPEN;
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit into wallpaper: "
-                    + AppTransition.appTransitionToString(transit));
-        } else {
-            mService.mAnimateWallpaperWithTarget = true;
+        }
+        // We never want to change from a Keyguard transit to a non-Keyguard transit, as our logic
+        // relies on the fact that we always execute a Keyguard transition after preparing one.
+        else if (!isKeyguardGoingAwayTransit(transit)) {
+            if (closingAppHasWallpaper && openingAppHasWallpaper) {
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!");
+                switch (transit) {
+                    case TRANSIT_ACTIVITY_OPEN:
+                    case TRANSIT_TASK_OPEN:
+                    case TRANSIT_TASK_TO_FRONT:
+                        transit = TRANSIT_WALLPAPER_INTRA_OPEN;
+                        break;
+                    case TRANSIT_ACTIVITY_CLOSE:
+                    case TRANSIT_TASK_CLOSE:
+                    case TRANSIT_TASK_TO_BACK:
+                        transit = TRANSIT_WALLPAPER_INTRA_CLOSE;
+                        break;
+                }
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                        "New transit: " + AppTransition.appTransitionToString(transit));
+            } else if (oldWallpaper != null && !mService.mOpeningApps.isEmpty()
+                    && !openingApps.contains(oldWallpaper.mAppToken)
+                    && closingApps.contains(oldWallpaper.mAppToken)) {
+                // We are transitioning from an activity with a wallpaper to one without.
+                transit = TRANSIT_WALLPAPER_CLOSE;
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit away from wallpaper: "
+                        + AppTransition.appTransitionToString(transit));
+            } else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw() &&
+                    openingApps.contains(wallpaperTarget.mAppToken)) {
+                // We are transitioning from an activity without
+                // a wallpaper to now showing the wallpaper
+                transit = TRANSIT_WALLPAPER_OPEN;
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit into wallpaper: "
+                        + AppTransition.appTransitionToString(transit));
+            } else {
+                mService.mAnimateWallpaperWithTarget = true;
+            }
         }
         return transit;
     }
diff --git a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
index 241ccf6..4e5c27f 100644
--- a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
+++ b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
@@ -71,7 +71,7 @@
 // auto-close it (otherwise there would be double-close problems).
 //
 // Rely upon the compiler to eliminate the constexprs used for clarity.
-hidl_handle&& handleFromFileDescriptor(base::unique_fd fd) {
+hidl_handle handleFromFileDescriptor(base::unique_fd fd) {
     hidl_handle h;
 
     NATIVE_HANDLE_DECLARE_STORAGE(storage, 0, 0);
@@ -83,7 +83,7 @@
     static constexpr bool kTakeOwnership = true;
     h.setTo(nh, kTakeOwnership);
 
-    return std::move(h);
+    return h;
 }
 
 }  // namespace
@@ -116,13 +116,14 @@
 
     bool rval;
     hidl_string msg;
-    configInterface->setHandles(h1, h2,
+    const auto status = configInterface->setHandles(h1, h2,
             [&rval, &msg](bool success, const hidl_string& errMsg) {
                 rval = success;
                 msg = errMsg;
             });
-    if (!rval) {
-        ALOGE("IOffloadConfig::setHandles() error: %s", msg.c_str());
+    if (!status.isOk() || !rval) {
+        ALOGE("IOffloadConfig::setHandles() error: '%s' / '%s'",
+              status.description().c_str(), msg.c_str());
     }
 
     return rval;
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index ec08874..ae98274 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -20,40 +20,9 @@
 import static android.app.Notification.GROUP_ALERT_SUMMARY;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 
-import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
-import com.android.server.lights.Light;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.Notification.Builder;
-import android.app.NotificationManager;
-import android.app.NotificationChannel;
-import android.graphics.Color;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.Vibrator;
-import android.os.VibrationEffect;
-import android.provider.Settings;
-import android.service.notification.StatusBarNotification;
-import android.support.test.runner.AndroidJUnit4;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.mockito.ArgumentMatcher;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyObject;
@@ -61,11 +30,43 @@
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.Notification.Builder;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.lights.Light;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class BuzzBeepBlinkTest extends NotificationTestCase {
@@ -163,6 +164,11 @@
                 true /* noisy */, false /* buzzy*/, false /* lights */);
     }
 
+    private NotificationRecord getInsistentBeepyLeanbackNotification() {
+        return getLeanbackNotificationRecord(mId, true /* insistent */, false /* once */,
+                true /* noisy */, false /* buzzy*/, false /* lights */);
+    }
+
     private NotificationRecord getBuzzyNotification() {
         return getNotificationRecord(mId, false /* insistent */, false /* once */,
                 false /* noisy */, true /* buzzy*/, false /* lights */);
@@ -192,23 +198,30 @@
         return getNotificationRecord(mId, false /* insistent */, true /* once */,
                 false /* noisy */, true /* buzzy*/, true /* lights */,
                 true /* defaultVibration */, true /* defaultSound */, false /* defaultLights */,
-                null, Notification.GROUP_ALERT_ALL);
+                null, Notification.GROUP_ALERT_ALL, false);
     }
 
     private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
             boolean noisy, boolean buzzy, boolean lights) {
         return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true,
-                null, Notification.GROUP_ALERT_ALL);
+                null, Notification.GROUP_ALERT_ALL, false);
+    }
+
+    private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent, boolean once,
+            boolean noisy, boolean buzzy, boolean lights) {
+        return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true,
+                null, Notification.GROUP_ALERT_ALL, true);
     }
 
     private NotificationRecord getBeepyNotificationRecord(String groupKey, int groupAlertBehavior) {
         return getNotificationRecord(mId, false, false, true, false, false, true, true, true,
-                groupKey, groupAlertBehavior);
+                groupKey, groupAlertBehavior, false);
     }
 
     private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
             boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
-            boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior) {
+            boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
+            boolean isLeanback) {
         NotificationChannel channel =
                 new NotificationChannel("test", "test", IMPORTANCE_HIGH);
         final Builder builder = new Builder(getContext())
@@ -257,9 +270,15 @@
             n.flags |= Notification.FLAG_INSISTENT;
         }
 
+        Context context = spy(getContext());
+        PackageManager packageManager = spy(context.getPackageManager());
+        when(context.getPackageManager()).thenReturn(packageManager);
+        when(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
+                .thenReturn(isLeanback);
+
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
                 mPid, n, mUser, null, System.currentTimeMillis());
-        NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+        NotificationRecord r = new NotificationRecord(context, sbn, channel);
         mService.addNotification(r);
         return r;
     }
@@ -367,6 +386,15 @@
     }
 
     @Test
+    public void testNoLeanbackBeep() throws Exception {
+        NotificationRecord r = getInsistentBeepyLeanbackNotification();
+
+        mService.buzzBeepBlinkLocked(r);
+
+        verifyNeverBeep();
+    }
+
+    @Test
     public void testNoInterruptionForMin() throws Exception {
         NotificationRecord r = getBeepyNotification();
         r.setImportance(NotificationManager.IMPORTANCE_MIN, "foo");
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index 0a4cb10..6090e35 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -359,6 +359,43 @@
     }
 
     @Test
+    public void testCancelAllNotificationsMultipleEnqueuedDoesNotCrash() throws Exception {
+        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        for (int i = 0; i < 10; i++) {
+            mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+                    sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        }
+        mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
+        waitForIdle();
+    }
+
+    @Test
+    public void testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash() throws Exception {
+        final NotificationRecord parent = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group1", true);
+        final NotificationRecord parentAsChild = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group1", false);
+        final NotificationRecord child = generateNotificationRecord(
+                mTestNotificationChannel, 2, "group1", false);
+
+        // fully post parent notification
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+                parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
+        waitForIdle();
+
+        // enqueue the child several times
+        for (int i = 0; i < 10; i++) {
+            mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+                    child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
+        }
+        // make the parent a child, which will cancel the child notification
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+                parentAsChild.sbn.getId(), parentAsChild.sbn.getNotification(),
+                parentAsChild.sbn.getUserId());
+        waitForIdle();
+    }
+
+    @Test
     public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
index 65a5632..6060881 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
@@ -107,7 +107,7 @@
                 createAppWindowController();
         controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
                 android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
-                false);
+                false, false);
         waitUntilHandlersIdle();
         final AppWindowToken atoken = controller.getAppWindowToken(mDisplayContent);
         assertHasStartingWindow(atoken);
@@ -125,7 +125,7 @@
                     createAppWindowController();
             controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
                     android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
-                    false);
+                    false, false);
             controller.removeStartingWindow();
             waitUntilHandlersIdle();
             assertNoStartingWindow(controller.getAppWindowToken(mDisplayContent));
@@ -140,11 +140,11 @@
                 createAppWindowController();
         controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
                 android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
-                false);
+                false, false);
         waitUntilHandlersIdle();
         controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
                 android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
-                true, true, false, true, false);
+                true, true, false, true, false, false);
         waitUntilHandlersIdle();
         assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent));
         assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent));
@@ -161,11 +161,11 @@
             // Surprise, ...! Transfer window in the middle of the creation flow.
             controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
                     android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
-                    true, true, false, true, false);
+                    true, true, false, true, false, false);
         });
         controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
                 android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
-                false);
+                false, false);
         waitUntilHandlersIdle();
         assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent));
         assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent));
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
index e2868d7..4288eac 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -62,7 +62,8 @@
         final TaskSnapshot snapshot = new TaskSnapshot(buffer,
                 ORIENTATION_PORTRAIT, contentInsets, false, 1.0f);
         mSurface = new TaskSnapshotSurface(sWm, new Window(), new Surface(), snapshot, "Test",
-                Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds);
+                Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds,
+                ORIENTATION_PORTRAIT);
     }
 
     private void setupSurface(int width, int height) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 1d5fb55..f53eb15 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -558,6 +558,13 @@
         }
     }
 
+    boolean isRecognitionRequested(UUID modelId) {
+        synchronized (mLock) {
+            ModelData modelData = mModelDataMap.get(modelId);
+            return modelData != null && modelData.isRequested();
+        }
+    }
+
     //---- SoundTrigger.StatusListener methods
     @Override
     public void onRecognition(RecognitionEvent event) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 9bca012..51c805d 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -16,18 +16,25 @@
 
 package com.android.server.soundtrigger;
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
 
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.Manifest;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.SoundModel;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
+import android.media.soundtrigger.SoundTriggerManager;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.ParcelUuid;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.util.Slog;
 
@@ -36,6 +43,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.TreeMap;
 import java.util.UUID;
 
 /**
@@ -52,16 +60,23 @@
     private static final boolean DEBUG = true;
 
     final Context mContext;
+    private Object mLock;
     private final SoundTriggerServiceStub mServiceStub;
     private final LocalSoundTriggerService mLocalSoundTriggerService;
     private SoundTriggerDbHelper mDbHelper;
     private SoundTriggerHelper mSoundTriggerHelper;
+    private final TreeMap<UUID, SoundModel> mLoadedModels;
+    private final TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback> mIntentCallbacks;
+    private PowerManager.WakeLock mWakelock;
 
     public SoundTriggerService(Context context) {
         super(context);
         mContext = context;
         mServiceStub = new SoundTriggerServiceStub();
         mLocalSoundTriggerService = new LocalSoundTriggerService(context);
+        mLoadedModels = new TreeMap<UUID, SoundModel>();
+        mIntentCallbacks = new TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback>();
+        mLock = new Object();
     }
 
     @Override
@@ -177,8 +192,357 @@
             mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
             mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
         }
+
+        @Override
+        public int loadGenericSoundModel(GenericSoundModel soundModel) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return STATUS_ERROR;
+            if (soundModel == null || soundModel.uuid == null) {
+                Slog.e(TAG, "Invalid sound model");
+                return STATUS_ERROR;
+            }
+            if (DEBUG) {
+                Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid);
+            }
+            synchronized (mLock) {
+                SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
+                // If the model we're loading is actually different than what we had loaded, we
+                // should unload that other model now. We don't care about return codes since we
+                // don't know if the other model is loaded.
+                if (oldModel != null && !oldModel.equals(soundModel)) {
+                    mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
+                    mIntentCallbacks.remove(soundModel.uuid);
+                }
+                mLoadedModels.put(soundModel.uuid, soundModel);
+            }
+            return STATUS_OK;
+        }
+
+        @Override
+        public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return STATUS_ERROR;
+            if (soundModel == null || soundModel.uuid == null) {
+                Slog.e(TAG, "Invalid sound model");
+                return STATUS_ERROR;
+            }
+            if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) {
+                Slog.e(TAG, "Only one keyphrase per model is currently supported.");
+                return STATUS_ERROR;
+            }
+            if (DEBUG) {
+                Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid);
+            }
+            synchronized (mLock) {
+                SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
+                // If the model we're loading is actually different than what we had loaded, we
+                // should unload that other model now. We don't care about return codes since we
+                // don't know if the other model is loaded.
+                if (oldModel != null && !oldModel.equals(soundModel)) {
+                    mSoundTriggerHelper.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id);
+                    mIntentCallbacks.remove(soundModel.uuid);
+                }
+                mLoadedModels.put(soundModel.uuid, soundModel);
+            }
+            return STATUS_OK;
+        }
+
+        @Override
+        public int startRecognitionForIntent(ParcelUuid soundModelId, PendingIntent callbackIntent,
+                SoundTrigger.RecognitionConfig config) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return STATUS_ERROR;
+            if (DEBUG) {
+                Slog.i(TAG, "startRecognition(): id = " + soundModelId);
+            }
+
+            synchronized (mLock) {
+                SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
+                if (soundModel == null) {
+                    Slog.e(TAG, soundModelId + " is not loaded");
+                    return STATUS_ERROR;
+                }
+                LocalSoundTriggerRecognitionStatusCallback callback = mIntentCallbacks.get(
+                        soundModelId.getUuid());
+                if (callback != null) {
+                    Slog.e(TAG, soundModelId + " is already running");
+                    return STATUS_ERROR;
+                }
+                callback = new LocalSoundTriggerRecognitionStatusCallback(soundModelId.getUuid(),
+                        callbackIntent, config);
+                int ret;
+                switch (soundModel.type) {
+                    case SoundModel.TYPE_KEYPHRASE: {
+                        KeyphraseSoundModel keyphraseSoundModel = (KeyphraseSoundModel) soundModel;
+                        ret = mSoundTriggerHelper.startKeyphraseRecognition(
+                                keyphraseSoundModel.keyphrases[0].id, keyphraseSoundModel, callback,
+                                config);
+                    } break;
+                    case SoundModel.TYPE_GENERIC_SOUND:
+                        ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid,
+                                (GenericSoundModel) soundModel, callback, config);
+                        break;
+                    default:
+                        Slog.e(TAG, "Unknown model type");
+                        return STATUS_ERROR;
+                }
+
+                if (ret != STATUS_OK) {
+                    Slog.e(TAG, "Failed to start model: " + ret);
+                    return ret;
+                }
+                mIntentCallbacks.put(soundModelId.getUuid(), callback);
+            }
+            return STATUS_OK;
+        }
+
+        @Override
+        public int stopRecognitionForIntent(ParcelUuid soundModelId) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return STATUS_ERROR;
+            if (DEBUG) {
+                Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
+            }
+
+            synchronized (mLock) {
+                SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
+                if (soundModel == null) {
+                    Slog.e(TAG, soundModelId + " is not loaded");
+                    return STATUS_ERROR;
+                }
+                LocalSoundTriggerRecognitionStatusCallback callback = mIntentCallbacks.get(
+                        soundModelId.getUuid());
+                if (callback == null) {
+                    Slog.e(TAG, soundModelId + " is not running");
+                    return STATUS_ERROR;
+                }
+                int ret;
+                switch (soundModel.type) {
+                    case SoundModel.TYPE_KEYPHRASE:
+                        ret = mSoundTriggerHelper.stopKeyphraseRecognition(
+                                ((KeyphraseSoundModel)soundModel).keyphrases[0].id, callback);
+                        break;
+                    case SoundModel.TYPE_GENERIC_SOUND:
+                        ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback);
+                        break;
+                    default:
+                        Slog.e(TAG, "Unknown model type");
+                        return STATUS_ERROR;
+                }
+
+                if (ret != STATUS_OK) {
+                    Slog.e(TAG, "Failed to stop model: " + ret);
+                    return ret;
+                }
+                mIntentCallbacks.remove(soundModelId.getUuid());
+            }
+            return STATUS_OK;
+        }
+
+        @Override
+        public int unloadSoundModel(ParcelUuid soundModelId) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return STATUS_ERROR;
+            if (DEBUG) {
+                Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
+            }
+
+            synchronized (mLock) {
+                SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
+                if (soundModel == null) {
+                    Slog.e(TAG, soundModelId + " is not loaded");
+                    return STATUS_ERROR;
+                }
+                int ret;
+                switch (soundModel.type) {
+                    case SoundModel.TYPE_KEYPHRASE:
+                        ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
+                                ((KeyphraseSoundModel)soundModel).keyphrases[0].id);
+                        break;
+                    case SoundModel.TYPE_GENERIC_SOUND:
+                        ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
+                        break;
+                    default:
+                        Slog.e(TAG, "Unknown model type");
+                        return STATUS_ERROR;
+                }
+                if (ret != STATUS_OK) {
+                    Slog.e(TAG, "Failed to unload model");
+                    return ret;
+                }
+                mLoadedModels.remove(soundModelId.getUuid());
+                return STATUS_OK;
+            }
+        }
+
+        @Override
+        public boolean isRecognitionActive(ParcelUuid parcelUuid) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return false;
+            synchronized (mLock) {
+                LocalSoundTriggerRecognitionStatusCallback callback =
+                        mIntentCallbacks.get(parcelUuid.getUuid());
+                if (callback == null) {
+                    return false;
+                }
+                return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
+            }
+        }
     }
 
+    private final class LocalSoundTriggerRecognitionStatusCallback
+            extends IRecognitionStatusCallback.Stub {
+        private UUID mUuid;
+        private PendingIntent mCallbackIntent;
+        private RecognitionConfig mRecognitionConfig;
+
+        public LocalSoundTriggerRecognitionStatusCallback(UUID modelUuid,
+                PendingIntent callbackIntent,
+                RecognitionConfig config) {
+            mUuid = modelUuid;
+            mCallbackIntent = callbackIntent;
+            mRecognitionConfig = config;
+        }
+
+        @Override
+        public boolean pingBinder() {
+            return mCallbackIntent != null;
+        }
+
+        @Override
+        public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
+            if (mCallbackIntent == null) {
+                return;
+            }
+            grabWakeLock();
+
+            Slog.w(TAG, "Keyphrase sound trigger event: " + event);
+            Intent extras = new Intent();
+            extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
+                    SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
+            extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
+            try {
+                mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
+                if (!mRecognitionConfig.allowMultipleTriggers) {
+                    removeCallback(/*releaseWakeLock=*/false);
+                }
+            } catch (PendingIntent.CanceledException e) {
+                removeCallback(/*releaseWakeLock=*/true);
+            }
+        }
+
+        @Override
+        public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
+            if (mCallbackIntent == null) {
+                return;
+            }
+            grabWakeLock();
+
+            Slog.w(TAG, "Generic sound trigger event: " + event);
+            Intent extras = new Intent();
+            extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
+                    SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
+            extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
+            try {
+                mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
+                if (!mRecognitionConfig.allowMultipleTriggers) {
+                    removeCallback(/*releaseWakeLock=*/false);
+                }
+            } catch (PendingIntent.CanceledException e) {
+                removeCallback(/*releaseWakeLock=*/true);
+            }
+        }
+
+        @Override
+        public void onError(int status) {
+            if (mCallbackIntent == null) {
+                return;
+            }
+            grabWakeLock();
+
+            Slog.i(TAG, "onError: " + status);
+            Intent extras = new Intent();
+            extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
+                    SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_ERROR);
+            extras.putExtra(SoundTriggerManager.EXTRA_STATUS, status);
+            try {
+                mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
+                // Remove the callback, but wait for the intent to finish before we let go of the
+                // wake lock
+                removeCallback(/*releaseWakeLock=*/false);
+            } catch (PendingIntent.CanceledException e) {
+                removeCallback(/*releaseWakeLock=*/true);
+            }
+        }
+
+        @Override
+        public void onRecognitionPaused() {
+            if (mCallbackIntent == null) {
+                return;
+            }
+            grabWakeLock();
+
+            Slog.i(TAG, "onRecognitionPaused");
+            Intent extras = new Intent();
+            extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
+                    SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED);
+            try {
+                mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
+            } catch (PendingIntent.CanceledException e) {
+                removeCallback(/*releaseWakeLock=*/true);
+            }
+        }
+
+        @Override
+        public void onRecognitionResumed() {
+            if (mCallbackIntent == null) {
+                return;
+            }
+            grabWakeLock();
+
+            Slog.i(TAG, "onRecognitionResumed");
+            Intent extras = new Intent();
+            extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
+                    SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED);
+            try {
+                mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
+            } catch (PendingIntent.CanceledException e) {
+                removeCallback(/*releaseWakeLock=*/true);
+            }
+        }
+
+        private void removeCallback(boolean releaseWakeLock) {
+            mCallbackIntent = null;
+            synchronized (mLock) {
+                mIntentCallbacks.remove(mUuid);
+                if (releaseWakeLock) {
+                    mWakelock.release();
+                }
+            }
+        }
+    }
+
+    private void grabWakeLock() {
+        synchronized (mLock) {
+            if (mWakelock == null) {
+                PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
+                mWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+            }
+            mWakelock.acquire();
+        }
+    }
+
+    private PendingIntent.OnFinished mCallbackCompletedHandler = new PendingIntent.OnFinished() {
+        @Override
+        public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
+                String resultData, Bundle resultExtras) {
+            // We're only ever invoked when the callback is done, so release the lock.
+            synchronized (mLock) {
+                mWakelock.release();
+            }
+        }
+    };
+
     public final class LocalSoundTriggerService extends SoundTriggerInternal {
         private final Context mContext;
         private SoundTriggerHelper mSoundTriggerHelper;
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 4390fae..31bb064 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -76,6 +76,13 @@
     public static final String EXTRA_CALL_SUBJECT_CHARACTER_ENCODING =
             "android.telecom.extra.CALL_SUBJECT_CHARACTER_ENCODING";
 
+     /**
+     * Indicating flag for phone account whether to use voip audio mode for voip calls
+     * @hide
+     */
+    public static final String EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE =
+            "android.telecom.extra.ALWAYS_USE_VOIP_AUDIO_MODE";
+
     /**
      * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
      * indicates whether this {@link PhoneAccount} is capable of supporting a request to handover a
diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java
index 0cb4cff..f15fde8 100644
--- a/telephony/java/android/telephony/NetworkScan.java
+++ b/telephony/java/android/telephony/NetworkScan.java
@@ -34,12 +34,26 @@
 
     public static final String TAG = "NetworkScan";
 
-    public static final int SUCCESS = 0;
-    public static final int ERROR_INVALID_SCAN = 1;
-    public static final int ERROR_UNSUPPORTED = 2;
-    public static final int ERROR_INTERRUPTED = 3;
-    public static final int ERROR_CANCELLED = 4;
+    // Below errors are mapped from RadioError which is returned from RIL. We will consolidate
+    // RadioErrors during the mapping if those RadioErrors mean no difference to the users.
+    public static final int SUCCESS = 0;                    // RadioError:NONE
+    public static final int ERROR_MODEM_ERROR = 1;          // RadioError:RADIO_NOT_AVAILABLE
+                                                            // RadioError:NO_MEMORY
+                                                            // RadioError:INTERNAL_ERR
+                                                            // RadioError:MODEM_ERR
+                                                            // RadioError:OPERATION_NOT_ALLOWED
+    public static final int ERROR_INVALID_SCAN = 2;         // RadioError:INVALID_ARGUMENTS
+    public static final int ERROR_MODEM_BUSY = 3;           // RadioError:DEVICE_IN_USE
+    public static final int ERROR_UNSUPPORTED = 4;          // RadioError:REQUEST_NOT_SUPPORTED
 
+    // Below errors are generated at the Telephony.
+    public static final int ERROR_RIL_ERROR = 10000;        // Nothing or only exception is
+                                                            // returned from RIL.
+    public static final int ERROR_INVALID_SCANID = 10001;   // The scanId is invalid. The user is
+                                                            // either trying to stop a scan which
+                                                            // does not exist or started by others.
+    public static final int ERROR_INTERRUPTED = 10002;      // Scan was interrupted by another scan
+                                                            // with higher priority.
     private final int mScanId;
     private final int mSubId;
 
diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java
index 0a542a7..d2aef20 100644
--- a/telephony/java/android/telephony/NetworkScanRequest.java
+++ b/telephony/java/android/telephony/NetworkScanRequest.java
@@ -31,6 +31,14 @@
  */
 public final class NetworkScanRequest implements Parcelable {
 
+    // Below size limits for RAN/Band/Channel are for pre-treble modems and will be removed later.
+    /** @hide */
+    public static final int MAX_RADIO_ACCESS_NETWORKS = 8;
+    /** @hide */
+    public static final int MAX_BANDS = 8;
+    /** @hide */
+    public static final int MAX_CHANNELS = 32;
+
     /** Performs the scan only once */
     public static final int SCAN_TYPE_ONE_SHOT = 0;
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 74327ce..4f78087 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -869,6 +869,20 @@
     public static final String EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC =
             "android.telephony.event.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC";
 
+    /**
+     * {@link android.telecom.Connection} event used to indicate that an outgoing call has been
+     * forwarded to another number.
+     * <p>
+     * Sent in response to an IMS supplementary service notification indicating the call has been
+     * forwarded.
+     * <p>
+     * Sent via {@link android.telecom.Connection#sendConnectionEvent(String, Bundle)}.
+     * The {@link Bundle} parameter is expected to be null when this connection event is used.
+     * @hide
+     */
+    public static final String EVENT_CALL_FORWARDED =
+            "android.telephony.event.EVENT_CALL_FORWARDED";
+
     /* Visual voicemail protocols */
 
     /**
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index be32f72..11770fb 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -92,15 +92,12 @@
     public static final int EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR = 1;
 
     /**
-     * Result code for an operation indicating that a generic error occurred.
+     * Result code for an operation indicating that an unresolvable error occurred.
      *
-     * <p>Note that in the future, other result codes may be returned indicating more specific
-     * errors. Thus, the caller should check for {@link #EMBEDDED_SUBSCRIPTION_RESULT_OK} or
-     * {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} to determine if the operation
-     * succeeded or failed with a user-resolvable error, and assume the operation failed for any
-     * other result, rather than checking for this specific value.
+     * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} will be populated with a detailed error
+     * code for logging/debugging purposes only.
      */
-    public static final int EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR = 2;
+    public static final int EMBEDDED_SUBSCRIPTION_RESULT_ERROR = 2;
 
     /**
      * Key for an extra set on {@link PendingIntent} result callbacks providing a detailed result
@@ -156,6 +153,12 @@
     public static final String EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT =
             "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT";
 
+    /**
+     * Optional meta-data attribute for a carrier app providing an icon to use to represent the
+     * carrier. If not provided, the app's launcher icon will be used as a fallback.
+     */
+    public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon";
+
     private final Context mContext;
     private final IEuiccController mController;
 
@@ -472,7 +475,7 @@
 
     private static void sendUnavailableError(PendingIntent callbackIntent) {
         try {
-            callbackIntent.send(EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR);
+            callbackIntent.send(EMBEDDED_SUBSCRIPTION_RESULT_ERROR);
         } catch (PendingIntent.CanceledException e) {
             // Caller canceled the callback; do nothing.
         }
diff --git a/tests/Internal/Android.mk b/tests/Internal/Android.mk
new file mode 100644
index 0000000..f59a624
--- /dev/null
+++ b/tests/Internal/Android.mk
@@ -0,0 +1,20 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+
+# Include some source files directly to be able to access package members
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test android-support-test
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_PACKAGE_NAME := InternalTests
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/Internal/AndroidManifest.xml b/tests/Internal/AndroidManifest.xml
new file mode 100644
index 0000000..a2c95fb
--- /dev/null
+++ b/tests/Internal/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.internal.tests">
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.internal.tests"
+                     android:label="Internal Tests" />
+</manifest>
diff --git a/tests/Internal/AndroidTest.xml b/tests/Internal/AndroidTest.xml
new file mode 100644
index 0000000..6531c93
--- /dev/null
+++ b/tests/Internal/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?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
+  -->
+<configuration description="Runs tests for internal classes/utilities.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="InternalTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="framework-base-presubmit" />
+    <option name="test-tag" value="InternalTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.internal.tests" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/Internal/src/com/android/internal/ml/clustering/KMeansTest.java b/tests/Internal/src/com/android/internal/ml/clustering/KMeansTest.java
new file mode 100644
index 0000000..a64f8a6
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/ml/clustering/KMeansTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.internal.ml.clustering;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.SuppressLint;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KMeansTest {
+
+    // Error tolerance (epsilon)
+    private static final double EPS = 0.01;
+
+    private KMeans mKMeans;
+
+    @Before
+    public void setUp() {
+        // Setup with a random seed to have predictable results
+        mKMeans = new KMeans(new Random(0), 30, 0);
+    }
+
+    @Test
+    public void getCheckDataSanityTest() {
+        try {
+            mKMeans.checkDataSetSanity(new float[][] {
+                    {0, 1, 2},
+                    {1, 2, 3}
+            });
+        } catch (IllegalArgumentException e) {
+            Assert.fail("Valid data didn't pass sanity check");
+        }
+
+        try {
+            mKMeans.checkDataSetSanity(new float[][] {
+                    null,
+                    {1, 2, 3}
+            });
+            Assert.fail("Data has null items and passed");
+        } catch (IllegalArgumentException e) {}
+
+        try {
+            mKMeans.checkDataSetSanity(new float[][] {
+                    {0, 1, 2, 4},
+                    {1, 2, 3}
+            });
+            Assert.fail("Data has invalid shape and passed");
+        } catch (IllegalArgumentException e) {}
+
+        try {
+            mKMeans.checkDataSetSanity(null);
+            Assert.fail("Null data should throw exception");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    @Test
+    public void sqDistanceTest() {
+        float a[] = {4, 10};
+        float b[] = {5, 2};
+        float sqDist = (float) (Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2));
+
+        assertEquals("Squared distance not valid", mKMeans.sqDistance(a, b), sqDist, EPS);
+    }
+
+    @Test
+    public void nearestMeanTest() {
+        KMeans.Mean meanA = new KMeans.Mean(0, 1);
+        KMeans.Mean meanB = new KMeans.Mean(1, 1);
+        List<KMeans.Mean> means = Arrays.asList(meanA, meanB);
+
+        KMeans.Mean nearest = mKMeans.nearestMean(new float[] {1, 1}, means);
+
+        assertEquals("Unexpected nearest mean for point {1, 1}", nearest, meanB);
+    }
+
+    @SuppressLint("DefaultLocale")
+    @Test
+    public void scoreTest() {
+        List<KMeans.Mean> closeMeans = Arrays.asList(new KMeans.Mean(0, 0.1f, 0.1f),
+                new KMeans.Mean(0, 0.1f, 0.15f),
+                new KMeans.Mean(0.1f, 0.2f, 0.1f));
+        List<KMeans.Mean> farMeans = Arrays.asList(new KMeans.Mean(0, 0, 0),
+                new KMeans.Mean(0, 0.5f, 0.5f),
+                new KMeans.Mean(1, 0.9f, 0.9f));
+
+        double closeScore = KMeans.score(closeMeans);
+        double farScore = KMeans.score(farMeans);
+        assertTrue(String.format("Score of well distributed means should be greater than "
+                + "close means but got: %f, %f", farScore, closeScore), farScore > closeScore);
+    }
+
+    @Test
+    public void predictTest() {
+        float[] expectedCentroid1 = {1, 1, 1};
+        float[] expectedCentroid2 = {0, 0, 0};
+        float[][] X = new float[][] {
+                {1, 1, 1},
+                {1, 1, 1},
+                {1, 1, 1},
+                {0, 0, 0},
+                {0, 0, 0},
+                {0, 0, 0},
+        };
+
+        final int numClusters = 2;
+
+        // Here we assume that we won't get stuck into a local optima.
+        // It's fine because we're seeding a random, we won't ever have
+        // unstable results but in real life we need multiple initialization
+        // and score comparison
+        List<KMeans.Mean> means = mKMeans.predict(numClusters, X);
+
+        assertEquals("Expected number of clusters is invalid", numClusters, means.size());
+
+        boolean exists1 = false, exists2 = false;
+        for (KMeans.Mean mean : means) {
+            if (Arrays.equals(mean.getCentroid(), expectedCentroid1)) {
+                exists1 = true;
+            } else if (Arrays.equals(mean.getCentroid(), expectedCentroid2)) {
+                exists2 = true;
+            } else {
+                throw new AssertionError("Unexpected mean: " + mean);
+            }
+        }
+        assertTrue("Expected means were not predicted, got: " + means,
+                exists1 && exists2);
+    }
+}
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index f9a30e9..2137e55 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -394,7 +394,6 @@
                 any(NetworkCallback.class), any(Handler.class));
         // In tethering mode, in the default configuration, an explicit request
         // for a mobile network is also made.
-        verify(mConnectivityManager, atLeastOnce()).getNetworkInfo(anyInt());
         verify(mConnectivityManager, times(1)).requestNetwork(
                 any(NetworkRequest.class), any(NetworkCallback.class), eq(0), anyInt(),
                 any(Handler.class));
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
index 4d340d1..14284d6 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -155,8 +155,7 @@
         lp.setInterfaceName(testIfName);
         offload.setUpstreamLinkProperties(lp);
         inOrder.verify(mHardware, times(1)).setUpstreamParameters(
-                eq(testIfName), eq(null), eq(null), mStringArrayCaptor.capture());
-        assertTrue(mStringArrayCaptor.getValue().isEmpty());
+                eq(testIfName), eq(null), eq(null), eq(null));
         inOrder.verifyNoMoreInteractions();
 
         final String ipv4Addr = "192.0.2.5";
@@ -164,16 +163,14 @@
         lp.addLinkAddress(new LinkAddress(linkAddr));
         offload.setUpstreamLinkProperties(lp);
         inOrder.verify(mHardware, times(1)).setUpstreamParameters(
-                eq(testIfName), eq(ipv4Addr), eq(null), mStringArrayCaptor.capture());
-        assertTrue(mStringArrayCaptor.getValue().isEmpty());
+                eq(testIfName), eq(ipv4Addr), eq(null), eq(null));
         inOrder.verifyNoMoreInteractions();
 
         final String ipv4Gateway = "192.0.2.1";
         lp.addRoute(new RouteInfo(InetAddress.getByName(ipv4Gateway)));
         offload.setUpstreamLinkProperties(lp);
         inOrder.verify(mHardware, times(1)).setUpstreamParameters(
-                eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
-        assertTrue(mStringArrayCaptor.getValue().isEmpty());
+                eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), eq(null));
         inOrder.verifyNoMoreInteractions();
 
         final String ipv6Gw1 = "fe80::cafe";
diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
index 57c258f..ce419a5 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -66,7 +66,6 @@
     @Mock private INetworkStatsService mStatsService;
     @Mock private IControlsTethering mTetherHelper;
     @Mock private InterfaceConfiguration mInterfaceConfiguration;
-    @Mock private IPv6TetheringInterfaceServices mIPv6TetheringInterfaceServices;
     @Mock private SharedLog mSharedLog;
 
     private final TestLooper mLooper = new TestLooper();
@@ -75,7 +74,7 @@
     private void initStateMachine(int interfaceType) throws Exception {
         mTestedSm = new TetherInterfaceStateMachine(
                 IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
-                mNMService, mStatsService, mTetherHelper, mIPv6TetheringInterfaceServices);
+                mNMService, mStatsService, mTetherHelper);
         mTestedSm.start();
         // Starting the state machine always puts us in a consistent state and notifies
         // the test of the world that we've changed from an unknown to available state.
@@ -102,8 +101,7 @@
     @Test
     public void startsOutAvailable() {
         mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
-                TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper,
-                mIPv6TetheringInterfaceServices);
+                TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper);
         mTestedSm.start();
         mLooper.dispatchAll();
         verify(mTetherHelper).updateInterfaceState(
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index 9bb392a..fb5c577 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -18,7 +18,12 @@
 
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -42,6 +47,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.NetworkState;
 import android.net.util.SharedLog;
 
 import android.support.test.filters.SmallTest;
@@ -59,6 +65,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -240,6 +247,84 @@
         assertFalse(mUNM.mobileNetworkRequested());
     }
 
+    @Test
+    public void testSelectPreferredUpstreamType() throws Exception {
+        final Collection<Integer> preferredTypes = new ArrayList<>();
+        preferredTypes.add(TYPE_WIFI);
+
+        mUNM.start();
+        // There are no networks, so there is nothing to select.
+        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
+
+        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
+        wifiAgent.fakeConnect();
+        // WiFi is up, we should prefer it.
+        assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
+        wifiAgent.fakeDisconnect();
+        // There are no networks, so there is nothing to select.
+        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
+
+        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+        cellAgent.fakeConnect();
+        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
+
+        preferredTypes.add(TYPE_MOBILE_DUN);
+        // This is coupled with preferred types in TetheringConfiguration.
+        mUNM.updateMobileRequiresDun(true);
+        // DUN is available, but only use regular cell: no upstream selected.
+        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
+        preferredTypes.remove(TYPE_MOBILE_DUN);
+        // No WiFi, but our preferred flavour of cell is up.
+        preferredTypes.add(TYPE_MOBILE_HIPRI);
+        // This is coupled with preferred types in TetheringConfiguration.
+        mUNM.updateMobileRequiresDun(false);
+        assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
+                mUNM.selectPreferredUpstreamType(preferredTypes));
+        // Check to see we filed an explicit request.
+        assertEquals(1, mCM.requested.size());
+        NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
+        assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
+        assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
+
+        wifiAgent.fakeConnect();
+        // WiFi is up, and we should prefer it over cell.
+        assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
+        assertEquals(0, mCM.requested.size());
+
+        preferredTypes.remove(TYPE_MOBILE_HIPRI);
+        preferredTypes.add(TYPE_MOBILE_DUN);
+        // This is coupled with preferred types in TetheringConfiguration.
+        mUNM.updateMobileRequiresDun(true);
+        assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
+
+        final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+        dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
+        dunAgent.fakeConnect();
+
+        // WiFi is still preferred.
+        assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
+
+        // WiFi goes down, cell and DUN are still up but only DUN is preferred.
+        wifiAgent.fakeDisconnect();
+        assertSatisfiesLegacyType(TYPE_MOBILE_DUN,
+                mUNM.selectPreferredUpstreamType(preferredTypes));
+        // Check to see we filed an explicit request.
+        assertEquals(1, mCM.requested.size());
+        netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
+        assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
+        assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
+    }
+
+    private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) {
+        if (legacyType == TYPE_NONE) {
+            assertTrue(ns == null);
+            return;
+        }
+
+        final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
+        assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities));
+    }
+
     private void assertUpstreamTypeRequested(int upstreamType) throws Exception {
         assertEquals(1, mCM.requested.size());
         assertEquals(1, mCM.legacyTypeMap.size());
@@ -254,6 +339,8 @@
         public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
         public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>();
 
+        private int mNetworkId = 100;
+
         public TestConnectivityManager(Context ctx, IConnectivityManager svc) {
             super(ctx, svc);
         }
@@ -287,6 +374,8 @@
             return false;
         }
 
+        int getNetworkId() { return ++mNetworkId; }
+
         @Override
         public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
             assertFalse(allCallbacks.containsKey(cb));
@@ -360,6 +449,35 @@
         }
     }
 
+    public static class TestNetworkAgent {
+        public final TestConnectivityManager cm;
+        public final Network networkId;
+        public final int transportType;
+        public final NetworkCapabilities networkCapabilities;
+
+        public TestNetworkAgent(TestConnectivityManager cm, int transportType) {
+            this.cm = cm;
+            this.networkId = new Network(cm.getNetworkId());
+            this.transportType = transportType;
+            networkCapabilities = new NetworkCapabilities();
+            networkCapabilities.addTransportType(transportType);
+            networkCapabilities.addCapability(NET_CAPABILITY_INTERNET);
+        }
+
+        public void fakeConnect() {
+            for (NetworkCallback cb : cm.listening.keySet()) {
+                cb.onAvailable(networkId);
+                cb.onCapabilitiesChanged(networkId, copy(networkCapabilities));
+            }
+        }
+
+        public void fakeDisconnect() {
+            for (NetworkCallback cb : cm.listening.keySet()) {
+                cb.onLost(networkId);
+            }
+        }
+    }
+
     public static class TestStateMachine extends StateMachine {
         public final ArrayList<Message> messages = new ArrayList<>();
         private final State mLoggingState = new LoggingState();
@@ -382,4 +500,8 @@
             super.start();
         }
     }
+
+    static NetworkCapabilities copy(NetworkCapabilities nc) {
+        return new NetworkCapabilities(nc);
+    }
 }