Merge "Fixes attestation id gathering from secondary android user"
diff --git a/api/current.txt b/api/current.txt
index e341739..e87c613 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4297,6 +4297,14 @@
     field public java.lang.String serviceDetails;
   }
 
+  public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable {
+    ctor public AuthenticationRequiredException(java.lang.Throwable, android.app.PendingIntent);
+    method public int describeContents();
+    method public android.app.PendingIntent getUserAction();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.AuthenticationRequiredException> CREATOR;
+  }
+
   public final class AutomaticZenRule implements android.os.Parcelable {
     ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, int, boolean);
     ctor public AutomaticZenRule(android.os.Parcel);
@@ -5708,17 +5716,6 @@
     field public static final int STYLE_SPINNER = 0; // 0x0
   }
 
-  public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
-    ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction);
-    method public int describeContents();
-    method public android.app.RemoteAction getUserAction();
-    method public java.lang.CharSequence getUserMessage();
-    method public void showAsDialog(android.app.Activity);
-    method public void showAsNotification(android.content.Context, java.lang.String);
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
-  }
-
   public final class RemoteAction implements android.os.Parcelable {
     ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
     method public android.app.RemoteAction clone();
@@ -6426,7 +6423,7 @@
     field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMERS = "android.app.extra.PROVISIONING_DISCLAIMERS";
     field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_CONTENT = "android.app.extra.PROVISIONING_DISCLAIMER_CONTENT";
     field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_HEADER = "android.app.extra.PROVISIONING_DISCLAIMER_HEADER";
-    field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
+    field public static final deprecated java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
     field public static final java.lang.String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
     field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
     field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
@@ -24621,6 +24618,7 @@
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+    field public static final java.lang.String COLUMN_LOCKED = "locked";
     field public static final java.lang.String COLUMN_NETWORK_AFFILIATION = "network_affiliation";
     field public static final java.lang.String COLUMN_ORIGINAL_NETWORK_ID = "original_network_id";
     field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
@@ -31197,7 +31195,6 @@
     method public final android.util.SizeF readSizeF();
     method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
     method public final android.util.SparseBooleanArray readSparseBooleanArray();
-    method public final android.util.SparseIntArray readSparseIntArray();
     method public final java.lang.String readString();
     method public final void readStringArray(java.lang.String[]);
     method public final void readStringList(java.util.List<java.lang.String>);
@@ -31242,7 +31239,6 @@
     method public final void writeSizeF(android.util.SizeF);
     method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
     method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
-    method public final void writeSparseIntArray(android.util.SparseIntArray);
     method public final void writeString(java.lang.String);
     method public final void writeStringArray(java.lang.String[]);
     method public final void writeStringList(java.util.List<java.lang.String>);
diff --git a/api/system-current.txt b/api/system-current.txt
index 2cc0186..0737299 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4441,6 +4441,14 @@
     field public java.lang.String serviceDetails;
   }
 
+  public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable {
+    ctor public AuthenticationRequiredException(java.lang.Throwable, android.app.PendingIntent);
+    method public int describeContents();
+    method public android.app.PendingIntent getUserAction();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.AuthenticationRequiredException> CREATOR;
+  }
+
   public final class AutomaticZenRule implements android.os.Parcelable {
     ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, int, boolean);
     ctor public AutomaticZenRule(android.os.Parcel);
@@ -5900,17 +5908,6 @@
     field public static final int STYLE_SPINNER = 0; // 0x0
   }
 
-  public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
-    ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction);
-    method public int describeContents();
-    method public android.app.RemoteAction getUserAction();
-    method public java.lang.CharSequence getUserMessage();
-    method public void showAsDialog(android.app.Activity);
-    method public void showAsNotification(android.content.Context, java.lang.String);
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
-  }
-
   public final class RemoteAction implements android.os.Parcelable {
     ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
     method public android.app.RemoteAction clone();
@@ -6654,7 +6651,7 @@
     field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMERS = "android.app.extra.PROVISIONING_DISCLAIMERS";
     field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_CONTENT = "android.app.extra.PROVISIONING_DISCLAIMER_CONTENT";
     field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_HEADER = "android.app.extra.PROVISIONING_DISCLAIMER_HEADER";
-    field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
+    field public static final deprecated java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
     field public static final java.lang.String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
     field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
     field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
@@ -33925,7 +33922,6 @@
     method public final android.util.SizeF readSizeF();
     method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
     method public final android.util.SparseBooleanArray readSparseBooleanArray();
-    method public final android.util.SparseIntArray readSparseIntArray();
     method public final java.lang.String readString();
     method public final void readStringArray(java.lang.String[]);
     method public final void readStringList(java.util.List<java.lang.String>);
@@ -33970,7 +33966,6 @@
     method public final void writeSizeF(android.util.SizeF);
     method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
     method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
-    method public final void writeSparseIntArray(android.util.SparseIntArray);
     method public final void writeString(java.lang.String);
     method public final void writeStringArray(java.lang.String[]);
     method public final void writeStringList(java.util.List<java.lang.String>);
diff --git a/api/test-current.txt b/api/test-current.txt
index 8aa352d..84d97fb 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4307,6 +4307,14 @@
     field public java.lang.String serviceDetails;
   }
 
+  public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable {
+    ctor public AuthenticationRequiredException(java.lang.Throwable, android.app.PendingIntent);
+    method public int describeContents();
+    method public android.app.PendingIntent getUserAction();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.AuthenticationRequiredException> CREATOR;
+  }
+
   public final class AutomaticZenRule implements android.os.Parcelable {
     ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, int, boolean);
     ctor public AutomaticZenRule(android.os.Parcel);
@@ -5719,17 +5727,6 @@
     field public static final int STYLE_SPINNER = 0; // 0x0
   }
 
-  public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
-    ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction);
-    method public int describeContents();
-    method public android.app.RemoteAction getUserAction();
-    method public java.lang.CharSequence getUserMessage();
-    method public void showAsDialog(android.app.Activity);
-    method public void showAsNotification(android.content.Context, java.lang.String);
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
-  }
-
   public final class RemoteAction implements android.os.Parcelable {
     ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
     method public android.app.RemoteAction clone();
@@ -6452,7 +6449,7 @@
     field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMERS = "android.app.extra.PROVISIONING_DISCLAIMERS";
     field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_CONTENT = "android.app.extra.PROVISIONING_DISCLAIMER_CONTENT";
     field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_HEADER = "android.app.extra.PROVISIONING_DISCLAIMER_HEADER";
-    field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
+    field public static final deprecated java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
     field public static final java.lang.String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
     field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
     field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
@@ -11751,6 +11748,15 @@
     field protected final java.util.ArrayList<T> mObservers;
   }
 
+  public final class PageViewCursor extends android.database.CursorWrapper implements android.database.CrossProcessCursor {
+    ctor public PageViewCursor(android.database.Cursor, int, int);
+    method public void fillWindow(int, android.database.CursorWindow);
+    method public android.database.CursorWindow getWindow();
+    method public boolean onMove(int, int);
+    method public static android.database.Cursor wrap(android.database.Cursor, android.os.Bundle);
+    field public static final java.lang.String EXTRA_AUTO_PAGED = "android.content.extra.AUTO_PAGED";
+  }
+
   public class SQLException extends java.lang.RuntimeException {
     ctor public SQLException();
     ctor public SQLException(java.lang.String);
@@ -24723,6 +24729,7 @@
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+    field public static final java.lang.String COLUMN_LOCKED = "locked";
     field public static final java.lang.String COLUMN_NETWORK_AFFILIATION = "network_affiliation";
     field public static final java.lang.String COLUMN_ORIGINAL_NETWORK_ID = "original_network_id";
     field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
@@ -31320,7 +31327,6 @@
     method public final android.util.SizeF readSizeF();
     method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
     method public final android.util.SparseBooleanArray readSparseBooleanArray();
-    method public final android.util.SparseIntArray readSparseIntArray();
     method public final java.lang.String readString();
     method public final void readStringArray(java.lang.String[]);
     method public final void readStringList(java.util.List<java.lang.String>);
@@ -31365,7 +31371,6 @@
     method public final void writeSizeF(android.util.SizeF);
     method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
     method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
-    method public final void writeSparseIntArray(android.util.SparseIntArray);
     method public final void writeString(java.lang.String);
     method public final void writeStringArray(java.lang.String[]);
     method public final void writeStringList(java.util.List<java.lang.String>);
diff --git a/core/java/android/app/AuthenticationRequiredException.java b/core/java/android/app/AuthenticationRequiredException.java
new file mode 100644
index 0000000..8960979
--- /dev/null
+++ b/core/java/android/app/AuthenticationRequiredException.java
@@ -0,0 +1,99 @@
+/*
+ * 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.app;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Specialization of {@link SecurityException} that is thrown when authentication is needed from the
+ * end user before viewing the content.
+ * <p>
+ * This exception is only appropriate where there is a concrete action the user can take to
+ * authorize and make forward progress, such as confirming or entering authentication credentials,
+ * or granting access via other means.
+ * <p class="note">
+ * Note: legacy code that receives this exception may treat it as a general
+ * {@link SecurityException}, and thus there is no guarantee that the action contained will be
+ * invoked by the user.
+ * </p>
+ */
+public final class AuthenticationRequiredException extends SecurityException implements Parcelable {
+    private static final String TAG = "AuthenticationRequiredException";
+
+    private final PendingIntent mUserAction;
+
+    /** {@hide} */
+    public AuthenticationRequiredException(Parcel in) {
+        this(new SecurityException(in.readString()), PendingIntent.CREATOR.createFromParcel(in));
+    }
+
+    /**
+     * Create an instance ready to be thrown.
+     *
+     * @param cause original cause with details designed for engineering
+     *            audiences.
+     * @param userAction primary action that will initiate the recovery. This
+     *            must launch an activity that is expected to set
+     *            {@link Activity#setResult(int)} before finishing to
+     *            communicate the final status of the recovery. For example,
+     *            apps that observe {@link Activity#RESULT_OK} may choose to
+     *            immediately retry their operation. If this exception was
+     *            thrown from a {@link ContentProvider}, you should also send
+     *            any relevant {@link ContentResolver#notifyChange} events to
+     *            trigger reloading of data.
+     */
+    public AuthenticationRequiredException(Throwable cause, PendingIntent userAction) {
+        super(cause.getMessage());
+        mUserAction = Preconditions.checkNotNull(userAction);
+    }
+
+    /**
+     * Return primary action that will initiate the authorization.
+     */
+    public PendingIntent getUserAction() {
+        return mUserAction;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(getMessage());
+        mUserAction.writeToParcel(dest, flags);
+    }
+
+    public static final Creator<AuthenticationRequiredException> CREATOR =
+            new Creator<AuthenticationRequiredException>() {
+        @Override
+        public AuthenticationRequiredException createFromParcel(Parcel source) {
+            return new AuthenticationRequiredException(source);
+        }
+
+        @Override
+        public AuthenticationRequiredException[] newArray(int size) {
+            return new AuthenticationRequiredException[size];
+        }
+    };
+}
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index eff77b5..44fefd3 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -1670,8 +1670,7 @@
             if (!mCheckedForLoaderManager) {
                 mCheckedForLoaderManager = true;
                 mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false);
-            }
-            if (mLoaderManager != null) {
+            } else if (mLoaderManager != null) {
                 mLoaderManager.doStart();
             }
         }
diff --git a/core/java/android/app/RecoverableSecurityException.java b/core/java/android/app/RecoverableSecurityException.java
index 8612f18..a503a46 100644
--- a/core/java/android/app/RecoverableSecurityException.java
+++ b/core/java/android/app/RecoverableSecurityException.java
@@ -45,7 +45,8 @@
  * Note: legacy code that receives this exception may treat it as a general
  * {@link SecurityException}, and thus there is no guarantee that the messages
  * contained will be shown to the end user.
- * </p>
+ *
+ * @hide
  */
 public final class RecoverableSecurityException extends SecurityException implements Parcelable {
     private static final String TAG = "RecoverableSecurityException";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3be4dd8..391885d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -535,18 +535,10 @@
             = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
 
     /**
-     * A String extra that, holds the email address of the account which a managed profile is
-     * created for. Used with {@link #ACTION_PROVISION_MANAGED_PROFILE} and
-     * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE}.
-     *
-     * <p> This extra is part of the {@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}.
-     *
-     * <p> If the {@link #ACTION_PROVISION_MANAGED_PROFILE} intent that starts managed provisioning
-     * contains this extra, it is forwarded in the
-     * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} intent to the mobile
-     * device management application that was set as the profile owner during provisioning.
-     * It is usually used to avoid that the user has to enter their email address twice.
+     * @deprecated From {@link android.os.Build.VERSION_CODES#O}, never used while provisioning the
+     * device.
      */
+    @Deprecated
     public static final String EXTRA_PROVISIONING_EMAIL_ADDRESS
         = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
 
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 4de64c4..59b022d 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -628,4 +628,6 @@
     ParceledListSlice getSharedLibraries(int flags, int userId);
 
     boolean canRequestPackageInstalls(String packageName, int userId);
+
+    void deletePreloadsFileCache();
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 2d073ab..fb69986 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -184,7 +184,7 @@
     private static final String TAG_RESTRICT_UPDATE = "restrict-update";
     private static final String TAG_USES_SPLIT = "uses-split";
 
-    // STOPSHIP remove the ability to expose components via meta-data
+    // [b/36551762] STOPSHIP remove the ability to expose components via meta-data
     // Temporary workaround; allow meta-data to expose components to instant apps
     private static final String META_DATA_INSTANT_APPS = "instantapps.clients.allowed";
 
diff --git a/core/java/android/database/PageViewCursor.java b/core/java/android/database/PageViewCursor.java
index 5f42f30..44727a0 100644
--- a/core/java/android/database/PageViewCursor.java
+++ b/core/java/android/database/PageViewCursor.java
@@ -18,13 +18,13 @@
 import static com.android.internal.util.Preconditions.checkArgument;
 
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.content.ContentResolver;
 import android.os.Build;
 import android.os.Bundle;
 import android.util.Log;
 import android.util.MathUtils;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
 /**
@@ -34,11 +34,10 @@
  *
  * @hide
  */
+@TestApi
 public final class PageViewCursor extends CursorWrapper implements CrossProcessCursor {
 
-    /**
-     * An in internal extra added to results that are auto-paged using the wrapper.
-     */
+    /** An extra added to results that are auto-paged using the wrapper. */
     public static final String EXTRA_AUTO_PAGED = "android.content.extra.AUTO_PAGED";
 
     private static final String TAG = "PageViewCursor";
@@ -56,7 +55,6 @@
     /**
      * @see PageViewCursor#wrap(Cursor, Bundle)
      */
-    @VisibleForTesting
     public PageViewCursor(Cursor cursor, int offset, int limit) {
         super(cursor);
 
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 8c13cc8..0218cb5 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -771,15 +771,15 @@
             3, // SENSOR_TYPE_GEOMAGNETIC_FIELD
             3, // SENSOR_TYPE_ORIENTATION
             3, // SENSOR_TYPE_GYROSCOPE
-            3, // SENSOR_TYPE_LIGHT
-            3, // SENSOR_TYPE_PRESSURE
-            3, // SENSOR_TYPE_TEMPERATURE
-            3, // SENSOR_TYPE_PROXIMITY
+            1, // SENSOR_TYPE_LIGHT
+            1, // SENSOR_TYPE_PRESSURE
+            1, // SENSOR_TYPE_TEMPERATURE
+            1, // SENSOR_TYPE_PROXIMITY
             3, // SENSOR_TYPE_GRAVITY
             3, // SENSOR_TYPE_LINEAR_ACCELERATION
             5, // SENSOR_TYPE_ROTATION_VECTOR
-            3, // SENSOR_TYPE_RELATIVE_HUMIDITY
-            3, // SENSOR_TYPE_AMBIENT_TEMPERATURE
+            1, // SENSOR_TYPE_RELATIVE_HUMIDITY
+            1, // SENSOR_TYPE_AMBIENT_TEMPERATURE
             6, // SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED
             4, // SENSOR_TYPE_GAME_ROTATION_VECTOR
             6, // SENSOR_TYPE_GYROSCOPE_UNCALIBRATED
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 7a39d23..2e35a51 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -898,6 +898,9 @@
         }
     }
 
+    /**
+     * @hide
+     */
     public final void writeSparseIntArray(SparseIntArray val) {
         if (val == null) {
             writeInt(-1);
@@ -2323,6 +2326,7 @@
     /**
      * Read and return a new SparseIntArray object from the parcel at the current
      * dataPosition(). Returns null if the previously written array object was null.
+     * @hide
      */
     public final SparseIntArray readSparseIntArray() {
         int N = readInt();
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 56d4ff7..9e56e01 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -1444,14 +1444,15 @@
      * <p>Providers are required to show confirmation UI for all new permissions granted
      * for the linked document.
      *
-     * <p>If list of recipients is known, then it can be passed in options as
-     * {@link Intent#EXTRA_EMAIL} as either a string or list of strings. Note, that
+     * <p>If list of recipients is known, then it should be passed in options as
+     * {@link Intent#EXTRA_EMAIL} as a list of email addresses. Note, that
      * this is just a hint for the provider, which can ignore the list. In either
      * case the provider is required to show a UI for letting the user confirm
      * any new permission grants.
      *
-     * <p>Note, that the entire <code>options</code> bundle is send to the provider.
-     * Make sure that you trust the provider before passing any sensitive information.
+     * <p>Note, that the entire <code>options</code> bundle will be sent to the provider
+     * backing the passed <code>uri</code>. Make sure that you trust the provider
+     * before passing any sensitive information.
      *
      * <p>Since this API may show a UI, it cannot be called from background.
      *
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 620d33a5..9e68afb 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -38,7 +38,7 @@
 import android.Manifest;
 import android.annotation.CallSuper;
 import android.annotation.Nullable;
-import android.app.RecoverableSecurityException;
+import android.app.AuthenticationRequiredException;
 import android.content.ClipDescription;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
@@ -235,10 +235,6 @@
      * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
      * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
      * not change once returned.
-     * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), but it is not guaranteed that
-     * the client will handle this properly.
      *
      * @param parentDocumentId the parent directory to create the new document
      *            under.
@@ -247,6 +243,10 @@
      * @param displayName the display name of the new document. The provider may
      *            alter this name to meet any internal constraints, such as
      *            avoiding conflicting names.
+
+     * @throws AuthenticationRequiredException If authentication is required from the user (such as
+     *             login credentials), but it is not guaranteed that the client will handle this
+     *             properly.
      */
     @SuppressWarnings("unused")
     public String createDocument(String parentDocumentId, String mimeType, String displayName)
@@ -262,15 +262,14 @@
      * URI permission grants will be updated to point at the new document. If
      * the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the
      * rename, return {@code null}.
-     * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), but it is not guaranteed that
-     * the client will handle this properly.
      *
      * @param documentId the document to rename.
      * @param displayName the updated display name of the document. The provider
      *            may alter this name to meet any internal constraints, such as
      *            avoiding conflicting names.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      */
     @SuppressWarnings("unused")
     public String renameDocument(String documentId, String displayName)
@@ -286,12 +285,11 @@
      * call (such as documents inside a directory) the implementor is
      * responsible for revoking those permissions using
      * {@link #revokeDocumentPermission(String)}.
-     * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), but it is not guaranteed that
-     * the client will handle this properly.
      *
      * @param documentId the document to delete.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      */
     @SuppressWarnings("unused")
     public void deleteDocument(String documentId) throws FileNotFoundException {
@@ -305,13 +303,12 @@
      * the same document provider. Upon completion returns the document id of
      * the copied document at the target destination. {@code null} must never
      * be returned.
-     * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), but it is not guaranteed that
-     * the client will handle this properly.
      *
      * @param sourceDocumentId the document to copy.
      * @param targetParentDocumentId the target document to be copied into as a child.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      */
     @SuppressWarnings("unused")
     public String copyDocument(String sourceDocumentId, String targetParentDocumentId)
@@ -329,15 +326,14 @@
      *
      * <p>It's the responsibility of the provider to revoke grants if the document
      * is no longer accessible using <code>sourceDocumentId</code>.
-     * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), but it is not guaranteed that
-     * the client will handle this properly.
      *
      * @param sourceDocumentId the document to move.
      * @param sourceParentDocumentId the parent of the document to move.
      * @param targetParentDocumentId the target document to be a new parent of the
      *     source document.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      */
     @SuppressWarnings("unused")
     public String moveDocument(String sourceDocumentId, String sourceParentDocumentId,
@@ -355,11 +351,11 @@
      * <p>It's the responsibility of the provider to revoke grants if the document is
      * removed from the last parent, and effectively the document is deleted.
      *
-     * <p>{@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), but it is not guaranteed that
-     * the client will handle this properly.
      * @param documentId the document to remove.
      * @param parentDocumentId the parent of the document to move.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      */
     @SuppressWarnings("unused")
     public void removeDocument(String documentId, String parentDocumentId)
@@ -377,9 +373,6 @@
      * <p>This API assumes that document ID has enough info to infer the root.
      * Different roots should use different document ID to refer to the same
      * document.
-     * <p>{@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), but it is not guaranteed that
-     * the client will handle this properly.perly.
      *
      *
      * @param parentDocumentId the document from which the path starts if not null,
@@ -388,6 +381,9 @@
      * @return the path of the requested document. If parentDocumentId is null
      *     returned root ID must not be null. If parentDocumentId is not null
      *     returned root ID must be null.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      */
     public Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId)
             throws FileNotFoundException {
@@ -397,7 +393,7 @@
     /**
      * Creates an intent sender for a web link, if the document is web linkable.
      * <p>
-     * {@link RecoverableSecurityException} can be thrown if user does not have
+     * {@link AuthenticationRequiredException} can be thrown if user does not have
      * sufficient permission for the linked document. Before any new permissions
      * are granted for the linked document, a visible UI must be shown, so the
      * user can explicitly confirm whether the permission grants are expected.
@@ -414,6 +410,9 @@
      *
      * @param documentId the document to create a web link intent for.
      * @param options additional information, such as list of recipients. Optional.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      *
      * @see DocumentsContract.Document#FLAG_WEB_LINKABLE
      * @see android.app.PendingIntent#getIntentSender
@@ -436,9 +435,6 @@
      * android.database.ContentObserver, boolean)} with
      * {@link DocumentsContract#buildRootsUri(String)} to notify the system.
      * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), or returned as part of
-     * Cursor's bundle. It is not guaranteed that the client will handle this properly.
      *
      * @param projection list of {@link Root} columns to put into the cursor. If
      *            {@code null} all supported columns should be included.
@@ -452,10 +448,6 @@
      * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and
      * limited to only return the 64 most recently modified documents.
      * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), or returned as part of
-     * Cursor's bundle. It is not guaranteed that the client will handle this properly.
-     * <p>
      * Recent documents do not support change notifications.
      *
      * @param projection list of {@link Document} columns to put into the
@@ -472,16 +464,14 @@
     /**
      * Return metadata for the single requested document. You should avoid
      * making network requests to keep this request fast.
-     * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), or returned as part of
-     * Cursor's bundle. It is not guaranteed that the client will handle this properly.
-     *
      *
      * @param documentId the document to return.
      * @param projection list of {@link Document} columns to put into the
      *            cursor. If {@code null} all supported columns should be
      *            included.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      */
     public abstract Cursor queryDocument(String documentId, String[] projection)
             throws FileNotFoundException;
@@ -509,11 +499,6 @@
      * you can call {@link ContentResolver#notifyChange(Uri,
      * android.database.ContentObserver, boolean)} with that Uri to send change
      * notifications.
-     * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), or returned as part of
-     * Cursor's bundle. It is not guaranteed that the client will handle this properly.
-     *
      *
      * @param parentDocumentId the directory to return children for.
      * @param projection list of {@link Document} columns to put into the
@@ -525,6 +510,9 @@
      *            may be unordered. This ordering is a hint that can be used to
      *            prioritize how data is fetched from the network, but UI may
      *            always enforce a specific ordering.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      * @see DocumentsContract#EXTRA_LOADING
      * @see DocumentsContract#EXTRA_INFO
      * @see DocumentsContract#EXTRA_ERROR
@@ -552,10 +540,6 @@
      * you can call {@link ContentResolver#notifyChange(Uri,
      * android.database.ContentObserver, boolean)} with that Uri to send change
      * notifications.
-     * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), or returned as part of
-     * Cursor's bundle. It is not guaranteed that the client will handle this properly.
      *
      * @param parentDocumentId the directory to return children for.
      * @param projection list of {@link Document} columns to put into the
@@ -567,6 +551,9 @@
      *            will be used, which may be unordered. See
      *            {@link ContentResolver#QUERY_ARG_SORT_COLUMNS} for
      *            details.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      *
      * @see DocumentsContract#EXTRA_LOADING
      * @see DocumentsContract#EXTRA_INFO
@@ -609,16 +596,16 @@
      * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
      * android.database.ContentObserver, boolean)} with that Uri to send change
      * notifications.
-     * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), or returned as part of
-     * Cursor's bundle. It is not guaranteed that the client will handle this properly.
      *
      * @param rootId the root to search under.
      * @param query string to match documents against.
      * @param projection list of {@link Document} columns to put into the
      *            cursor. If {@code null} all supported columns should be
      *            included.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     *
      * @see DocumentsContract#EXTRA_LOADING
      * @see DocumentsContract#EXTRA_INFO
      * @see DocumentsContract#EXTRA_ERROR
@@ -641,9 +628,9 @@
      * implementation queries {@link #queryDocument(String, String[])}, so
      * providers may choose to override this as an optimization.
      * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), but it is not guaranteed that
-     * the client will handle this properly.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      */
     public String getDocumentType(String documentId) throws FileNotFoundException {
         final Cursor cursor = queryDocument(documentId, null);
@@ -669,15 +656,14 @@
      * <p>
      * If you block while downloading content, you should periodically check
      * {@link CancellationSignal#isCanceled()} to abort abandoned open requests.
-     * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), but it is not guaranteed that
-     * the client will handle this properly.
      *
      * @param documentId the document to return.
      * @param mode the mode to open with, such as 'r', 'w', or 'rw'.
      * @param signal used by the caller to signal if the request should be
      *            cancelled. May be null.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler,
      *      OnCloseListener)
      * @see ParcelFileDescriptor#createReliablePipe()
@@ -697,15 +683,14 @@
      * If you perform expensive operations to download or generate a thumbnail,
      * you should periodically check {@link CancellationSignal#isCanceled()} to
      * abort abandoned thumbnail requests.
-     * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), but it is not guaranteed that
-     * the client will handle this properly.
      *
      * @param documentId the document to return.
      * @param sizeHint hint of the optimal thumbnail dimensions.
      * @param signal used by the caller to signal if the request should be
      *            cancelled. May be null.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      * @see Document#FLAG_SUPPORTS_THUMBNAIL
      */
     @SuppressWarnings("unused")
@@ -723,10 +708,6 @@
      * matching the specified MIME type filter.
      * <p>
      * Virtual documents must have at least one streamable format.
-     * <p>
-     * {@link RecoverableSecurityException} can be thrown if more input is required
-     * from the user (such as insufficient permission), but it is not guaranteed that
-     * the client will handle this properly.
      *
      * @param documentId the document to return.
      * @param mimeTypeFilter the MIME type filter for the requested format. May
@@ -735,6 +716,9 @@
      *            provider.
      * @param signal used by the caller to signal if the request should be
      *            cancelled. May be null.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
      * @see #getDocumentStreamTypes(String, String)
      */
     @SuppressWarnings("unused")
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 353dfed..9a2e0bb 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -322,6 +322,8 @@
 
         /**
          * Enables or disables paragraph justification. The default value is disabled (false).
+         * If the last line is too short for justification, the last line will be displayed with
+         * the alignment set by {@link #setAlignment}.
          *
          * @param justify true for enabling and false for disabling paragraph justification.
          * @return this builder, useful for chaining.
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index d096baf..26b3ae2 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -1620,17 +1620,32 @@
             int anchorHeight, int drawingLocationY, int screenLocationY, int displayFrameTop,
             int displayFrameBottom, boolean allowResize) {
         final int winOffsetY = screenLocationY - drawingLocationY;
-        final int anchorTopInScreen = outParams.y + winOffsetY;
-        final int spaceBelow = displayFrameBottom - anchorTopInScreen;
-        if (anchorTopInScreen >= 0 && height <= spaceBelow) {
+        final int popupScreenTop = outParams.y + winOffsetY;
+        final int spaceBelow = displayFrameBottom - popupScreenTop;
+        if (popupScreenTop >= 0 && height <= spaceBelow) {
             return true;
         }
 
-        final int spaceAbove = anchorTopInScreen - anchorHeight - displayFrameTop;
+        final int popupScreenBottom;
+        if (mOverlapAnchor) {
+            // popupScreenTop equals the anchor's top at this point.
+            // When shown above the anchor, an overlapping popup's bottom should be aligned with
+            // the anchor's bottom.
+            popupScreenBottom = popupScreenTop + anchorHeight;
+        } else {
+            // popupScreenTop equals the anchor's bottom at this point.
+            // When shown above the anchor, a non-overlapping popup's bottom is aligned with
+            // the anchor's top.
+            popupScreenBottom = popupScreenTop - anchorHeight;
+        }
+        final int spaceAbove = popupScreenBottom - displayFrameTop;
         if (height <= spaceAbove) {
             // Move everything up.
             if (mOverlapAnchor) {
-                yOffset += anchorHeight;
+                // Add one anchorHeight to compensate for the correction made at the start of
+                // findDropDownPosition, and another to account for being aligned to the anchor's
+                // bottom, not top.
+                yOffset += anchorHeight * 2;
             }
             outParams.y = drawingLocationY - height + yOffset;
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f2a7f25..8d0ea08 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3743,7 +3743,9 @@
     }
 
     /**
-     * Enables or disables full justification. The default value is false.
+     * Enables or disables full justification. The default value is false. If the last line is too
+     * short for justification, the last line will be displayed with the alignment set by
+     * {@link android.view.View#setTextAlignment}.
      *
      * @see #getJustify()
      */
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6aa7766..9d2141d 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -31,12 +31,15 @@
 import android.os.Build;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.IBatteryPropertiesRegistrar;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
 import android.os.ParcelFormatException;
 import android.os.Parcelable;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.WorkSource;
@@ -3151,6 +3154,7 @@
         boolean unpluggedScreenOff = unplugged && screenOff;
         if (unpluggedScreenOff != mOnBatteryScreenOffTimeBase.isRunning()) {
             updateKernelWakelocksLocked();
+            updateBatteryPropertiesLocked();
             if (DEBUG_ENERGY_CPU) {
                 Slog.d(TAG, "Updating cpu time because screen is now " +
                         (unpluggedScreenOff ? "off" : "on"));
@@ -3160,6 +3164,16 @@
         }
     }
 
+    private void updateBatteryPropertiesLocked() {
+        try {
+            IBatteryPropertiesRegistrar registrar = IBatteryPropertiesRegistrar.Stub.asInterface(
+                    ServiceManager.getService("batteryproperties"));
+            registrar.scheduleUpdate();
+        } catch (RemoteException e) {
+            // Ignore.
+        }
+    }
+
     public void addIsolatedUidLocked(int isolatedUid, int appUid) {
         mIsolatedUids.put(isolatedUid, appUid);
     }
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index a8d6830..de5e505 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -621,7 +621,7 @@
     const uint8_t* s = (const uint8_t*)src;
     do {
         uint8_t c = *s++;
-        *dst++ = SkColorSetARGB(c, c, c, c);
+        *dst++ = SkColorSetARGB(c, 0, 0, 0);
     } while (--width != 0);
 }
 
@@ -727,6 +727,50 @@
     return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable));
 }
 
+static bool bitmapCopyTo(SkBitmap* dst, SkColorType dstCT, const SkBitmap& src,
+        SkBitmap::Allocator* alloc) {
+    // Skia does not support copying from kAlpha8 to types that are not alpha only.
+    // We will handle this case here.
+    if (kAlpha_8_SkColorType == src.colorType() && kAlpha_8_SkColorType != dstCT) {
+        SkAutoPixmapUnlock srcUnlocker;
+        if (!src.requestLock(&srcUnlocker)) {
+            return false;
+        }
+        SkPixmap srcPixmap = srcUnlocker.pixmap();
+
+        SkImageInfo dstInfo = src.info().makeColorType(dstCT);
+        if (!dst->setInfo(dstInfo)) {
+            return false;
+        }
+        if (!dst->tryAllocPixels(alloc, nullptr)) {
+            return false;
+        }
+
+        switch (dstCT) {
+            case kRGBA_8888_SkColorType:
+            case kBGRA_8888_SkColorType: {
+                for (int y = 0; y < src.height(); y++) {
+                    const uint8_t* srcRow = srcPixmap.addr8(0, y);
+                    uint32_t* dstRow = dst->getAddr32(0, y);
+                    ToColor_SA8(dstRow, srcRow, src.width(), nullptr);
+                }
+                return true;
+            }
+            case kRGB_565_SkColorType: {
+                for (int y = 0; y < src.height(); y++) {
+                    uint16_t* dstRow = dst->getAddr16(0, y);
+                    memset(dstRow, 0, sizeof(uint16_t) * src.width());
+                }
+                return true;
+            }
+            default:
+                return false;
+        }
+    }
+
+    return src.copyTo(dst, dstCT, alloc);
+}
+
 static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle,
                            jint dstConfigHandle, jboolean isMutable) {
     SkBitmap src;
@@ -743,7 +787,7 @@
     SkBitmap result;
     HeapAllocator allocator;
 
-    if (!src.copyTo(&result, dstCT, &allocator)) {
+    if (!bitmapCopyTo(&result, dstCT, src, &allocator)) {
         return NULL;
     }
     auto bitmap = allocator.getStorageObjAndReset();
@@ -754,7 +798,7 @@
     SkBitmap result;
 
     AshmemPixelAllocator allocator(env);
-    if (!src.copyTo(&result, dstCT, &allocator)) {
+    if (!bitmapCopyTo(&result, dstCT, src, &allocator)) {
         return NULL;
     }
     auto bitmap = allocator.getStorageObjAndReset();
diff --git a/core/res/res/drawable-hdpi/stat_notify_mmcc_indication_icn.png b/core/res/res/drawable-hdpi/stat_notify_mmcc_indication_icn.png
new file mode 100644
index 0000000..d704951
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_notify_mmcc_indication_icn.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/stat_notify_mmcc_indication_icn.png b/core/res/res/drawable-xhdpi/stat_notify_mmcc_indication_icn.png
new file mode 100644
index 0000000..5dfc89e
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/stat_notify_mmcc_indication_icn.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/stat_notify_mmcc_indication_icn.png b/core/res/res/drawable-xxhdpi/stat_notify_mmcc_indication_icn.png
new file mode 100644
index 0000000..a648b0b
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/stat_notify_mmcc_indication_icn.png
Binary files differ
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d4db258..d1c5900 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4601,4 +4601,10 @@
 
     <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for others -->
     <string name="etws_primary_default_message_others"></string>
+
+    <!-- Title of notification when UE fails to register network with MM reject cause code. -->
+    <string name="mmcc_authentication_reject">SIM not allowed</string>
+    <string name="mmcc_imsi_unknown_in_hlr">SIM not provisioned</string>
+    <string name="mmcc_illegal_ms">SIM not allowed</string>
+    <string name="mmcc_illegal_me">Phone not allowed</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4ef3922..11cde7a5 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1326,6 +1326,8 @@
   <java-symbol type="drawable" name="ic_sim_card_multi_24px_clr" />
   <java-symbol type="drawable" name="ic_sim_card_multi_48px_clr" />
 
+  <java-symbol type="drawable" name="stat_notify_mmcc_indication_icn" />
+
   <java-symbol type="drawable" name="ic_account_circle" />
   <java-symbol type="color" name="user_icon_1" />
   <java-symbol type="color" name="user_icon_2" />
@@ -1911,6 +1913,10 @@
   <java-symbol type="string" name="low_internal_storage_view_text" />
   <java-symbol type="string" name="low_internal_storage_view_text_no_boot" />
   <java-symbol type="string" name="low_internal_storage_view_title" />
+  <java-symbol type="string" name="mmcc_authentication_reject" />
+  <java-symbol type="string" name="mmcc_imsi_unknown_in_hlr" />
+  <java-symbol type="string" name="mmcc_illegal_ms" />
+  <java-symbol type="string" name="mmcc_illegal_me" />
   <java-symbol type="string" name="notification_listener_binding_label" />
   <java-symbol type="string" name="vr_listener_binding_label" />
   <java-symbol type="string" name="condition_provider_service_binding_label" />
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 042bac6..228d950 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -358,10 +358,10 @@
                         FileChannel.MapMode.READ_ONLY, 0, fontSize);
                 int style = result.getStyle();
                 int weight = (style & BOLD) != 0 ? 700 : 400;
-                // TODO: this method should be
-                // create(fd, ttcIndex, fontVariationSettings, style).
+                final ArrayList<FontConfig.Axis> axes = FontListParser.parseFontVariationSettings(
+                        result.getFontVariationSettings());
                 if (!fontFamily.addFontFromBuffer(fontBuffer, result.getTtcIndex(),
-                                null, weight,
+                                axes.toArray(new FontConfig.Axis[axes.size()]), weight,
                                 (style & ITALIC) == 0 ? Builder.NORMAL : Builder.ITALIC)) {
                     Log.e(TAG, "Error creating font " + request.getQuery());
                     callback.onTypefaceRequestFailed(
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 9981668..5c64566 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -196,7 +196,8 @@
      *
      * @deprecated Use {@link #ACTION_KEYCHAIN_CHANGED}, {@link #ACTION_TRUST_STORE_CHANGED} or
      * {@link #ACTION_KEY_ACCESS_CHANGED}. Apps that target a version higher than
-     * {@link Build.VERSION_CODES#N_MR1} will not receive this broadcast.
+     * {@link Build.VERSION_CODES#N_MR1} will only receive this broadcast if they register for it
+     * at runtime.
      */
     public static final String ACTION_STORAGE_CHANGED = "android.security.STORAGE_CHANGED";
 
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index cbea501..652954b 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -389,10 +389,10 @@
             return new ProjectionTestCanvas(mDrawCounter);
         }
         sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override {
-            return sk_sp<SkSurface>();
+            return nullptr;
         }
-        sk_sp<SkImage> onNewImageSnapshot(SkBudgeted) override {
-            return sk_sp<SkImage>();
+        sk_sp<SkImage> onNewImageSnapshot() override {
+            return nullptr;
         }
         void onCopyOnWrite(ContentChangeMode) override {}
         int* mDrawCounter;
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index c28aa5e..6808b57 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -1971,10 +1971,11 @@
          * channel is not locked thus the user is not prompted to enter passcode If not specified,
          * this value is set to 0 (not locked) by default.
          *
+         * <p>This column can only be set by applications having proper system permission to
+         * modify parental control settings. For other applications, this is a read-only column.
+
          * <p>Type: INTEGER (boolean)
-         * @hide
          */
-        @SystemApi
         public static final String COLUMN_LOCKED = "locked";
 
         /**
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8dc694c..d6d01d8a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1382,11 +1382,15 @@
         of notifications.  Replaces the channel name and only appears when there is more than one channel. -->
     <string name="notification_num_channels"> <xliff:g id="number">%d</xliff:g> notification categories</string>
 
+    <!-- Notification: Control panel: Label that shows when an app has not upgraded to use channels.
+        Hints that the user's only option is to block all of the app's notifications. -->
+    <string name="notification_default_channel_desc">This app doesn\'t have notification categories</string>
+
     <!-- Notification: Control panel: Label that shows how many channels this application has
-        defined, describing the current notification channel as "1 out of n categories from this app". -->
+        defined, describing the current notification channel as "1 out of n notification categories from this app". -->
     <plurals name="notification_num_channels_desc">
-        <item quantity="one">1 out of <xliff:g id="number">%d</xliff:g> category from this app</item>
-        <item quantity="other">1 out of <xliff:g id="number">%d</xliff:g> categories from this app</item>
+        <item quantity="one">1 out of <xliff:g id="number">%d</xliff:g> notification category from this app</item>
+        <item quantity="other">1 out of <xliff:g id="number">%d</xliff:g> notification categories from this app</item>
     </plurals>
 
     <!-- Notification: Control panel: For bundles of notifications, this label that lists the
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 043490c..2d0fe6f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -20,6 +20,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.service.quicksettings.Tile;
 import android.text.TextUtils;
 import android.view.Gravity;
 import android.view.View;
@@ -110,7 +111,6 @@
 
     public void init(OnClickListener click, OnClickListener secondaryClick,
             OnLongClickListener longClick) {
-        setClickable(true);
         setOnClickListener(click);
         setOnLongClickListener(longClick);
     }
@@ -148,6 +148,7 @@
     }
 
     protected void handleStateChanged(QSTile.State state) {
+        setClickable(state.state != Tile.STATE_UNAVAILABLE);
         mIcon.setIcon(state);
         setContentDescription(state.contentDescription);
         mAccessibilityClass = state.expandedAccessibilityClassName;
@@ -157,6 +158,12 @@
     }
 
     @Override
+    public void setClickable(boolean clickable) {
+        super.setClickable(clickable);
+        setBackground(clickable ? mRipple : null);
+    }
+
+    @Override
     public int getDetailY() {
         return getTop() + getHeight() / 2;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index ca70336..4351b2c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -137,7 +137,8 @@
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.value = mDataController.isMobileDataSupported()
                 && mDataController.isMobileDataEnabled();
-        state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+        state.state = cb.airplaneModeEnabled ? Tile.STATE_UNAVAILABLE
+                : state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index a9043e4..54921a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -99,11 +99,14 @@
         mINotificationManager = iNotificationManager;
         mPkg = pkg;
         mNotificationChannels = notificationChannels;
+        boolean isSingleDefaultChannel = false;
         if (mNotificationChannels.isEmpty()) {
             throw new IllegalArgumentException("bindNotification requires at least one channel");
         } else if (mNotificationChannels.size() == 1) {
             mSingleNotificationChannel = mNotificationChannels.get(0);
             mStartingUserImportance = mSingleNotificationChannel.getImportance();
+            isSingleDefaultChannel = mSingleNotificationChannel.getId()
+                    .equals(NotificationChannel.DEFAULT_CHANNEL_ID);
         } else {
             mSingleNotificationChannel = null;
         }
@@ -135,24 +138,30 @@
 
         String channelsDescText;
         mNumChannelsView = (TextView) (findViewById(R.id.num_channels_desc));
-        switch (mNotificationChannels.size()) {
-            case 1:
-                channelsDescText = String.format(mContext.getResources().getQuantityString(
-                        R.plurals.notification_num_channels_desc, numChannels), numChannels);
-                break;
-            case 2:
-                channelsDescText = mContext.getString(R.string.notification_channels_list_desc_2,
-                        mNotificationChannels.get(0).getName(),
-                        mNotificationChannels.get(1).getName());
-                break;
-            default:
-                final int numOthers = mNotificationChannels.size() - 2;
-                channelsDescText = String.format(
-                        mContext.getResources().getQuantityString(
-                                R.plurals.notification_channels_list_desc_2_and_others, numOthers),
-                        mNotificationChannels.get(0).getName(),
-                        mNotificationChannels.get(1).getName(),
-                        numOthers);
+        if (isSingleDefaultChannel) {
+            channelsDescText = mContext.getString(R.string.notification_default_channel_desc);
+        } else {
+            switch (mNotificationChannels.size()) {
+                case 1:
+                    channelsDescText = String.format(mContext.getResources().getQuantityString(
+                            R.plurals.notification_num_channels_desc, numChannels), numChannels);
+                    break;
+                case 2:
+                    channelsDescText = mContext.getString(
+                            R.string.notification_channels_list_desc_2,
+                            mNotificationChannels.get(0).getName(),
+                            mNotificationChannels.get(1).getName());
+                    break;
+                default:
+                    final int numOthers = mNotificationChannels.size() - 2;
+                    channelsDescText = String.format(
+                            mContext.getResources().getQuantityString(
+                                    R.plurals.notification_channels_list_desc_2_and_others,
+                                    numOthers),
+                            mNotificationChannels.get(0).getName(),
+                            mNotificationChannels.get(1).getName(),
+                            numOthers);
+            }
         }
         mNumChannelsView.setText(channelsDescText);
 
@@ -160,9 +169,8 @@
             // Multiple channels don't use a channel name for the title.
             channelNameText = mContext.getString(R.string.notification_num_channels,
                     mNotificationChannels.size());
-        } else if (mSingleNotificationChannel.getId()
-                .equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
-            // If this is the placeholder channel, don't use our channel-specific text.
+        } else if (isSingleDefaultChannel) {
+            // If this is the default channel, don't use our channel-specific text.
             channelNameText = mContext.getString(R.string.notification_header_default_channel);
         } else {
             channelNameText = mSingleNotificationChannel.getName();
@@ -282,15 +290,9 @@
     }
 
     private void updateSecondaryText() {
-        final boolean defaultChannel = mSingleNotificationChannel != null &&
-                mSingleNotificationChannel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID);
         final boolean disabled = mSingleNotificationChannel != null &&
                 getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
-        if (defaultChannel) {
-            // Don't show any secondary text if this is from the default channel.
-            mChannelDisabledView.setVisibility(View.GONE);
-            mNumChannelsView.setVisibility(View.GONE);
-        } else if (disabled) {
+        if (disabled) {
             mChannelDisabledView.setVisibility(View.VISIBLE);
             mNumChannelsView.setVisibility(View.GONE);
         } else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 8aca546..5632b71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -260,12 +260,14 @@
     }
 
     @Test
-    public void testBindNotification_NumChannelsTextHiddenWhenDefaultChannel() throws Exception {
+    public void testBindNotification_NumChannelsTextUniqueWhenDefaultChannel() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel), null, null, null);
         final TextView numChannelsView =
                 (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
-        assertTrue(numChannelsView.getVisibility() != View.VISIBLE);
+        assertEquals(View.VISIBLE, numChannelsView.getVisibility());
+        assertEquals(mContext.getString(R.string.notification_default_channel_desc),
+                numChannelsView.getText());
     }
 
     @Test
@@ -390,13 +392,14 @@
 
     @Test
     @UiThreadTest
-    public void testBindNotification_ChannelDisabledTextHiddenWhenDefaultChannel()
+    public void testBindNotification_ChannelDisabledTextShowsForDefaultChannel()
             throws Exception {
+        mDefaultNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel), null, null, null);
         final TextView channelDisabledView =
                 (TextView) mNotificationInfo.findViewById(R.id.channel_disabled);
-        assertTrue(channelDisabledView.getVisibility() != View.VISIBLE);
+        assertEquals(View.VISIBLE, channelDisabledView.getVisibility());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 8808988..f516d74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -20,7 +20,9 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothProfile;
+import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
 
 import com.android.settingslib.bluetooth.BluetoothEventManager;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -31,10 +33,13 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.List;
 
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
 public class BluetoothControllerImplTest extends SysuiTestCase {
 
     private LocalBluetoothManager mMockBluetoothManager;
@@ -47,7 +52,7 @@
 
     @Before
     public void setup() throws Exception {
-        mTestableLooper = new TestableLooper();
+        mTestableLooper = TestableLooper.get(this);
         mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
         mDevices = new ArrayList<>();
         mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 01fc8ec..8e3e3ea 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5393,7 +5393,7 @@
 
     @NonNull
     private Account[] filterAccounts(UserAccounts accounts, Account[] unfiltered, int callingUid,
-            String callingPackage, boolean includeManagedNotVisible) {
+            @Nullable String callingPackage, boolean includeManagedNotVisible) {
         String visibilityFilterPackage = callingPackage;
         if (visibilityFilterPackage == null) {
             visibilityFilterPackage = getPackageNameForUid(callingUid);
@@ -5429,8 +5429,7 @@
         }
         UserInfo user = getUserManager().getUserInfo(userAccounts.userId);
         if (user != null && user.isRestricted()) {
-            String[] packages =
-                    mPackageManager.getPackagesForUid(callingUid);
+            String[] packages = mPackageManager.getPackagesForUid(callingUid);
             if (packages == null) {
                 packages = new String[] {};
             }
@@ -5501,9 +5500,6 @@
     @NonNull
     protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType,
             int callingUid, @Nullable String callingPackage, boolean includeManagedNotVisible) {
-        if (callingPackage == null) {
-            callingPackage = getPackageNameForUid(callingUid);
-        }
         if (accountType != null) {
             final Account[] accounts = userAccounts.accountCache.get(accountType);
             if (accounts == null) {
diff --git a/services/core/java/com/android/server/fingerprint/EnumerateClient.java b/services/core/java/com/android/server/fingerprint/EnumerateClient.java
index 34f245f..1b8b89c 100644
--- a/services/core/java/com/android/server/fingerprint/EnumerateClient.java
+++ b/services/core/java/com/android/server/fingerprint/EnumerateClient.java
@@ -58,7 +58,7 @@
     public int stop(boolean initiatedByClient) {
         IBiometricsFingerprint daemon = getFingerprintDaemon();
         if (daemon == null) {
-            Slog.w(TAG, "stopAuthentication: no fingerprint HAL!");
+            Slog.w(TAG, "stopEnumeration: no fingerprint HAL!");
             return ERROR_ESRCH;
         }
         try {
@@ -102,12 +102,12 @@
     @Override
     public boolean onEnrollResult(int fingerId, int groupId, int rem) {
         if (DEBUG) Slog.w(TAG, "onEnrollResult() called for enumerate!");
-        return true; // Invalid for Remove
+        return true; // Invalid for Enumerate.
     }
 
     @Override
     public boolean onRemoved(int fingerId, int groupId, int remaining) {
         if (DEBUG) Slog.w(TAG, "onRemoved() called for enumerate!");
-        return true; // Invalid for Authenticate
+        return true; // Invalid for Enumerate.
     }
 }
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 7d97ce4..b6e7932 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -85,6 +85,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.LinkedList;
 
 /**
  * A service to manage multiple clients that want to access the fingerprint HAL API.
@@ -134,6 +135,20 @@
     private ClientMonitor mPendingClient;
     private PerformanceStats mPerformanceStats;
 
+
+    private IBinder mToken = new Binder(); // used for internal FingerprintService enumeration
+    private LinkedList<Integer> mEnumeratingUserIds = new LinkedList<>();
+    private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw finterprints
+
+    private class UserFingerprint {
+        Fingerprint f;
+        int userId;
+        public UserFingerprint(Fingerprint f, int userId) {
+            this.f = f;
+            this.userId = userId;
+        }
+    }
+
     // Normal fingerprint authentications are tracked by mPerformanceMap.
     private HashMap<Integer, PerformanceStats> mPerformanceMap = new HashMap<>();
 
@@ -257,10 +272,12 @@
         // This operation can be expensive, so keep track of the elapsed time. Might need to move to
         // background if it takes too long.
         long t = System.currentTimeMillis();
-
         mAuthenticatorIds.clear();
+        mEnumeratingUserIds.clear();
+        mUnknownFingerprints.clear();
         for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
             int userId = getUserOrWorkProfileId(null, user.id);
+            mEnumeratingUserIds.add(userId);
             if (!mAuthenticatorIds.containsKey(userId)) {
                 updateActiveGroup(userId, null);
             }
@@ -270,12 +287,70 @@
         if (t > 1000) {
             Slog.w(TAG, "loadAuthenticatorIds() taking too long: " + t + "ms");
         }
+
+        if (!mEnumeratingUserIds.isEmpty()) {
+            enumerateNextUser();
+        }
+    }
+
+    private void enumerateNextUser() {
+        int nextUser = mEnumeratingUserIds.getFirst();
+        updateActiveGroup(nextUser, null);
+        boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
+
+        if (DEBUG) Slog.v(TAG, "Enumerating user id " + nextUser + " of "
+                + mEnumeratingUserIds.size() + " remaining users");
+
+        startEnumerate(mToken, nextUser, null, restricted, true /* internal */);
+    }
+
+    // Remove unknown fingerprints from hardware
+    private void cleanupUnknownFingerprints() {
+        if (!mUnknownFingerprints.isEmpty()) {
+            Slog.w(TAG, "unknown fingerprint size: " + mUnknownFingerprints.size());
+            UserFingerprint uf = mUnknownFingerprints.get(0);
+            mUnknownFingerprints.remove(uf);
+            boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
+            updateActiveGroup(uf.userId, null);
+            startRemove(mToken, uf.f.getFingerId(), uf.f.getGroupId(), uf.userId, null,
+                    restricted, true /* internal */);
+        }
     }
 
     protected void handleEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
-        if (DEBUG) Slog.w(TAG, "Enumerate: fid=" + fingerId + ", gid="
-                + groupId + "rem=" + remaining);
-        // TODO: coordinate names with framework
+        if (DEBUG) Slog.w(TAG, "Enumerate: fid=" + fingerId
+                + ", gid=" + groupId
+                + ", dev=" + deviceId
+                + ", rem=" + remaining);
+
+        ClientMonitor client = mCurrentClient;
+
+        if (client != null) {
+            client.onEnumerationResult(fingerId, groupId, remaining);
+        }
+
+        // All fingerprints in hardware for this user were enumerated
+        if (remaining == 0) {
+            mEnumeratingUserIds.poll();
+
+            if (client instanceof InternalEnumerateClient) {
+                List<Fingerprint> enrolled = ((InternalEnumerateClient) client).getEnumeratedList();
+                Slog.w(TAG, "Added " + enrolled.size() + " enumerated fingerprints for deletion");
+                for (Fingerprint f : enrolled) {
+                    mUnknownFingerprints.add(new UserFingerprint(f, client.getTargetUserId()));
+                }
+            }
+
+            removeClient(client);
+
+            if (!mEnumeratingUserIds.isEmpty()) {
+                enumerateNextUser();
+            } else if (client instanceof InternalEnumerateClient) {
+                if (DEBUG) Slog.v(TAG, "Finished enumerating all users");
+                // This will start a chain of InternalRemovalClients
+                cleanupUnknownFingerprints();
+            }
+        }
     }
 
     protected void handleError(long deviceId, int error, int vendorCode) {
@@ -304,10 +379,18 @@
     }
 
     protected void handleRemoved(long deviceId, int fingerId, int groupId, int remaining) {
+        if (DEBUG) Slog.w(TAG, "Removed: fid=" + fingerId
+                + ", gid=" + groupId
+                + ", dev=" + deviceId
+                + ", rem=" + remaining);
+
         ClientMonitor client = mCurrentClient;
         if (client != null && client.onRemoved(fingerId, groupId, remaining)) {
             removeClient(client);
         }
+        if (client instanceof InternalRemovalClient && !mUnknownFingerprints.isEmpty()) {
+            cleanupUnknownFingerprints();
+        }
     }
 
     protected void handleAuthenticated(long deviceId, int fingerId, int groupId,
@@ -434,7 +517,15 @@
         ClientMonitor currentClient = mCurrentClient;
         if (currentClient != null) {
             if (DEBUG) Slog.v(TAG, "request stop current client " + currentClient.getOwnerString());
-            currentClient.stop(initiatedByClient);
+            if (currentClient instanceof InternalEnumerateClient ||
+                    currentClient instanceof InternalRemovalClient) {
+                // This condition means we're currently running internal diagnostics to
+                // remove extra fingerprints in the hardware and/or the software
+                // TODO: design an escape hatch in case client never finishes
+            }
+            else {
+                currentClient.stop(initiatedByClient);
+            }
             mPendingClient = newClient;
             mHandler.removeCallbacks(mResetClientState);
             mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
@@ -451,47 +542,86 @@
     }
 
     void startRemove(IBinder token, int fingerId, int groupId, int userId,
-            IFingerprintServiceReceiver receiver, boolean restricted) {
+            IFingerprintServiceReceiver receiver, boolean restricted, boolean internal) {
         IBiometricsFingerprint daemon = getFingerprintDaemon();
         if (daemon == null) {
             Slog.w(TAG, "startRemove: no fingerprint HAL!");
             return;
         }
-        RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token,
-                receiver, fingerId, groupId, userId, restricted, token.toString()) {
-            @Override
-            public void notifyUserActivity() {
-                FingerprintService.this.userActivity();
-            }
 
-            @Override
-            public IBiometricsFingerprint getFingerprintDaemon() {
-                return FingerprintService.this.getFingerprintDaemon();
-            }
-        };
-        startClient(client, true);
+        if (internal) {
+            Context context = getContext();
+            InternalRemovalClient client = new InternalRemovalClient(context, mHalDeviceId,
+                    token, receiver, fingerId, groupId, userId, restricted,
+                    context.getOpPackageName()) {
+                @Override
+                public void notifyUserActivity() {
+
+                }
+                @Override
+                public IBiometricsFingerprint getFingerprintDaemon() {
+                    return FingerprintService.this.getFingerprintDaemon();
+                }
+            };
+            startClient(client, true);
+        }
+        else {
+            RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token,
+                    receiver, fingerId, groupId, userId, restricted, token.toString()) {
+                @Override
+                public void notifyUserActivity() {
+                    FingerprintService.this.userActivity();
+                }
+
+                @Override
+                public IBiometricsFingerprint getFingerprintDaemon() {
+                    return FingerprintService.this.getFingerprintDaemon();
+                }
+            };
+            startClient(client, true);
+        }
     }
 
     void startEnumerate(IBinder token, int userId,
-        IFingerprintServiceReceiver receiver, boolean restricted) {
+        IFingerprintServiceReceiver receiver, boolean restricted, boolean internal) {
         IBiometricsFingerprint daemon = getFingerprintDaemon();
         if (daemon == null) {
             Slog.w(TAG, "startEnumerate: no fingerprint HAL!");
             return;
         }
-        EnumerateClient client = new EnumerateClient(getContext(), mHalDeviceId, token,
-            receiver, userId, userId, restricted, token.toString()) {
-            @Override
-            public void notifyUserActivity() {
-                FingerprintService.this.userActivity();
-            }
+        if (internal) {
+            List<Fingerprint> enrolledList = getEnrolledFingerprints(userId);
+            Context context = getContext();
+            InternalEnumerateClient client = new InternalEnumerateClient(context, mHalDeviceId,
+                    token, receiver, userId, userId, restricted, context.getOpPackageName(),
+                    enrolledList) {
+                @Override
+                public void notifyUserActivity() {
 
-            @Override
-            public IBiometricsFingerprint getFingerprintDaemon() {
-                return FingerprintService.this.getFingerprintDaemon();
-            }
-        };
-        startClient(client, true);
+                }
+
+                @Override
+                public IBiometricsFingerprint getFingerprintDaemon() {
+                    return FingerprintService.this.getFingerprintDaemon();
+                }
+            };
+            startClient(client, true);
+        }
+        else {
+            EnumerateClient client = new EnumerateClient(getContext(), mHalDeviceId, token,
+                    receiver, userId, userId, restricted, token.toString()) {
+                @Override
+                public void notifyUserActivity() {
+                    FingerprintService.this.userActivity();
+                }
+
+                @Override
+                public IBiometricsFingerprint getFingerprintDaemon() {
+                    return FingerprintService.this.getFingerprintDaemon();
+                }
+            };
+            startClient(client, true);
+        }
     }
 
     public List<Fingerprint> getEnrolledFingerprints(int userId) {
@@ -978,12 +1108,14 @@
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    startRemove(token, fingerId, groupId, userId, receiver, restricted);
+                    startRemove(token, fingerId, groupId, userId, receiver,
+                            restricted, false /* internal */);
                 }
             });
 
         }
 
+        @Override // Binder call
         public void enumerate(final IBinder token, final int userId,
             final IFingerprintServiceReceiver receiver) {
             checkPermission(MANAGE_FINGERPRINT); // TODO: Maybe have another permission
@@ -991,7 +1123,7 @@
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    startEnumerate(token, userId, receiver, restricted);
+                    startEnumerate(token, userId, receiver, restricted, false /* internal */);
                 }
             });
 
diff --git a/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java b/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java
new file mode 100644
index 0000000..f4d2596
--- /dev/null
+++ b/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java
@@ -0,0 +1,94 @@
+/*
+ * 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.fingerprint;
+
+import android.content.Context;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.IBinder;
+import android.util.Slog;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An internal class to help clean up unknown fingerprints in the hardware and software
+ */
+public abstract class InternalEnumerateClient extends EnumerateClient {
+
+    private List<Fingerprint> mEnrolledList;
+    private List<Fingerprint> mEnumeratedList = new ArrayList<>(); // list of fp to delete
+
+    public InternalEnumerateClient(Context context, long halDeviceId, IBinder token,
+            IFingerprintServiceReceiver receiver, int groupId, int userId,
+            boolean restricted, String owner, List<Fingerprint> enrolledList) {
+
+        super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner);
+        mEnrolledList = enrolledList;
+    }
+
+    private void handleEnumeratedFingerprint(int fingerId, int groupId, int remaining) {
+
+        boolean matched = false;
+        for (int i=0; i<mEnrolledList.size(); i++) {
+            if (mEnrolledList.get(i).getFingerId() == fingerId) {
+                mEnrolledList.remove(i);
+                matched = true;
+                Slog.e(TAG, "Matched fingerprint fid=" + fingerId);
+                break;
+            }
+        }
+
+        // fingerId 0 means no fingerprints are in hardware
+        if (!matched && fingerId != 0) {
+            Fingerprint fingerprint = new Fingerprint("", groupId, fingerId, getHalDeviceId());
+            mEnumeratedList.add(fingerprint);
+        }
+    }
+
+    private void doFingerprintCleanup() {
+
+        if (mEnrolledList == null) {
+            return;
+        }
+
+        for (Fingerprint f : mEnrolledList) {
+            Slog.e(TAG, "Internal Enumerate: Removing dangling enrolled fingerprint: "
+                    + f.getName() + " " + f.getFingerId() + " " + f.getGroupId()
+                    + " " + f.getDeviceId());
+
+            FingerprintUtils.getInstance().removeFingerprintIdForUser(getContext(),
+                    f.getFingerId(), getTargetUserId());
+        }
+        mEnrolledList.clear();
+    }
+
+    public List<Fingerprint> getEnumeratedList() {
+        return mEnumeratedList;
+    }
+
+    @Override
+    public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
+
+        handleEnumeratedFingerprint(fingerId, groupId, remaining);
+        if (remaining == 0) {
+            doFingerprintCleanup();
+        }
+
+        return fingerId == 0; // done when id hits 0
+    }
+
+}
diff --git a/services/core/java/com/android/server/fingerprint/InternalRemovalClient.java b/services/core/java/com/android/server/fingerprint/InternalRemovalClient.java
new file mode 100644
index 0000000..19f61fe
--- /dev/null
+++ b/services/core/java/com/android/server/fingerprint/InternalRemovalClient.java
@@ -0,0 +1,33 @@
+/*
+ * 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.fingerprint;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import com.android.server.fingerprint.RemovalClient;
+
+public abstract class InternalRemovalClient extends RemovalClient {
+
+    public InternalRemovalClient(Context context, long halDeviceId, IBinder token,
+            IFingerprintServiceReceiver receiver, int fingerId, int groupId, int userId,
+            boolean restricted, String owner) {
+
+        super(context, halDeviceId, token, receiver, fingerId, groupId, userId, restricted, owner);
+
+    }
+}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 11cc52d..0774779 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -583,7 +583,7 @@
                     ServiceInfo info = mPm.getServiceInfo(component,
                             PackageManager.MATCH_DIRECT_BOOT_AWARE
                                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userIds[i]);
-                    if (!mConfig.bindPermission.equals(info.permission)) {
+                    if (info == null || !mConfig.bindPermission.equals(info.permission)) {
                         Slog.w(TAG, "Skipping " + getCaption() + " service " + component
                                 + ": it does not require the permission " + mConfig.bindPermission);
                         continue;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3727a5b..ede5a5e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -179,6 +179,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -1441,11 +1442,6 @@
                 return ;
             }
 
-            if (isCallerInstantApp(pkg)) {
-                throw new SecurityException("Instant app " + pkg
-                        + " is not allowed to create toasts");
-            }
-
             final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
             final boolean isPackageSuspended =
                     isPackageSuspendedForUser(pkg, Binder.getCallingUid());
@@ -3121,8 +3117,19 @@
                         + " - notification=" + notification);
                 return;
             }
-            throw new IllegalArgumentException("No Channel found for channelId=" + channelId
-                    + ", notification=" + notification);
+            final String noChannelStr = "No Channel found for "
+                    + "pkg=" + pkg
+                    + ", channelId=" + channelId
+                    + ", opPkg=" + opPkg
+                    + ", callingUid=" + callingUid
+                    + ", userId=" + userId
+                    + ", incomingUserId=" + incomingUserId
+                    + ", notificationUid=" + notificationUid
+                    + ", notification=" + notification;
+            // STOPSHIP TODO: should throw instead of logging.
+            // throw new IllegalArgumentException(noChannelStr);
+            Log.e(TAG, noChannelStr);
+            return;
         }
         final StatusBarNotification n = new StatusBarNotification(
                 pkg, opPkg, id, tag, notificationUid, callingPid, notification,
@@ -3645,6 +3652,10 @@
             mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
                     effect, record.getAudioAttributes());
             return true;
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Error creating vibration waveform with pattern: " +
+                    Arrays.toString(vibration));
+            return false;
         } finally{
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index cb499d8..f5ea74d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2879,24 +2879,17 @@
             }
 
             mInstallerService = new PackageInstallerService(context, this);
-
             final ComponentName ephemeralResolverComponent = getEphemeralResolverLPr();
             if (ephemeralResolverComponent != null) {
                 if (DEBUG_EPHEMERAL) {
-                    Slog.i(TAG, "Ephemeral resolver: " + ephemeralResolverComponent);
+                    Slog.d(TAG, "Set ephemeral resolver: " + ephemeralResolverComponent);
                 }
                 mInstantAppResolverConnection =
                         new EphemeralResolverConnection(mContext, ephemeralResolverComponent);
             } else {
                 mInstantAppResolverConnection = null;
             }
-            mInstantAppInstallerComponent = getEphemeralInstallerLPr();
-            if (mInstantAppInstallerComponent != null) {
-                if (DEBUG_EPHEMERAL) {
-                    Slog.i(TAG, "Ephemeral installer: " + mInstantAppInstallerComponent);
-                }
-                setUpInstantAppInstallerActivityLP(mInstantAppInstallerComponent);
-            }
+            updateInstantAppInstallerLocked();
 
             // Read and update the usage of dex files.
             // Do this at the end of PM init so that all the packages have their
@@ -2936,6 +2929,21 @@
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
+    private void updateInstantAppInstallerLocked() {
+        final ComponentName oldInstantAppInstallerComponent = mInstantAppInstallerComponent;
+        final ComponentName newInstantAppInstallerComponent = getEphemeralInstallerLPr();
+        if (newInstantAppInstallerComponent != null
+                && !newInstantAppInstallerComponent.equals(oldInstantAppInstallerComponent)) {
+            if (DEBUG_EPHEMERAL) {
+                Slog.d(TAG, "Set ephemeral installer: " + newInstantAppInstallerComponent);
+            }
+            setUpInstantAppInstallerActivityLP(newInstantAppInstallerComponent);
+        } else if (DEBUG_EPHEMERAL && newInstantAppInstallerComponent == null) {
+            Slog.d(TAG, "Unset ephemeral installer; none available");
+        }
+        mInstantAppInstallerComponent = newInstantAppInstallerComponent;
+    }
+
     private static File preparePackageParserCache(boolean isUpgrade) {
         if (!DEFAULT_PACKAGE_PARSER_CACHE_ENABLED) {
             return null;
@@ -3811,6 +3819,16 @@
     }
 
     @Override
+    public void deletePreloadsFileCache() {
+        if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.SYSTEM_UID)) {
+            throw new SecurityException("Only system or settings may call deletePreloadsFileCache");
+        }
+        File dir = Environment.getDataPreloadsFileCacheDirectory();
+        Slog.i(TAG, "Deleting preloaded file cache " + dir);
+        FileUtils.deleteContents(dir);
+    }
+
+    @Override
     public void freeStorageAndNotify(final String volumeUuid, final long freeStorageSize,
             final IPackageDataObserver observer) {
         mContext.enforceCallingOrSelfPermission(
@@ -3863,19 +3881,27 @@
     public void freeStorage(String volumeUuid, long bytes, int storageFlags) throws IOException {
         final StorageManager storage = mContext.getSystemService(StorageManager.class);
         final File file = storage.findPathForUuid(volumeUuid);
+        if (file.getUsableSpace() >= bytes) return;
 
         if (ENABLE_FREE_CACHE_V2) {
             final boolean aggressive = (storageFlags
                     & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0;
+            final boolean internalVolume = Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL,
+                    volumeUuid);
 
             // 1. Pre-flight to determine if we have any chance to succeed
             // 2. Consider preloaded data (after 1w honeymoon, unless aggressive)
+            if (internalVolume && (aggressive || SystemProperties
+                    .getBoolean("persist.sys.preloads.file_cache_expired", false))) {
+                deletePreloadsFileCache();
+                if (file.getUsableSpace() >= bytes) return;
+            }
 
             // 3. Consider parsed APK data (aggressive only)
-            if (aggressive) {
+            if (internalVolume && aggressive) {
                 FileUtils.deleteContents(mCacheDir);
+                if (file.getUsableSpace() >= bytes) return;
             }
-            if (file.getUsableSpace() >= bytes) return;
 
             // 4. Consider cached app data (above quotas)
             try {
@@ -16928,6 +16954,7 @@
 
             if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                 updateSequenceNumberLP(pkgName, res.newUsers);
+                updateInstantAppInstallerLocked();
             }
         }
     }
@@ -17503,6 +17530,7 @@
                         mInstantAppRegistry.onPackageUninstalledLPw(pkg, info.removedUsers);
                     }
                     updateSequenceNumberLP(packageName, info.removedUsers);
+                    updateInstantAppInstallerLocked();
                 }
             }
         }
@@ -19848,6 +19876,12 @@
             }
             scheduleWritePackageRestrictionsLocked(userId);
             updateSequenceNumberLP(packageName, new int[] { userId });
+            final long callingId = Binder.clearCallingIdentity();
+            try {
+                updateInstantAppInstallerLocked();
+            } finally {
+                Binder.restoreCallingIdentity(callingId);
+            }
             components = mPendingBroadcasts.get(userId, packageName);
             final boolean newPackage = components == null;
             if (newPackage) {
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index 17e5e9f..342ec4b 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -29,9 +29,10 @@
     private static final boolean DEBUG = false;
 
     private final Context mContext;
-    private final LegacyGlobalActions mLegacyGlobalActions;
     private final StatusBarManagerInternal mStatusBarInternal;
     private final Handler mHandler;
+    private final WindowManagerFuncs mWindowManagerFuncs;
+    private LegacyGlobalActions mLegacyGlobalActions;
     private boolean mKeyguardShowing;
     private boolean mDeviceProvisioned;
     private boolean mStatusBarConnected;
@@ -40,10 +41,19 @@
     public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {
         mContext = context;
         mHandler = new Handler();
-        mLegacyGlobalActions = new LegacyGlobalActions(context, windowManagerFuncs,
-                this::onGlobalActionsDismissed);
+        mWindowManagerFuncs = windowManagerFuncs;
         mStatusBarInternal = LocalServices.getService(StatusBarManagerInternal.class);
-        mStatusBarInternal.setGlobalActionsListener(this);
+
+        // Some form factors do not have a status bar.
+        if (mStatusBarInternal != null) {
+            mStatusBarInternal.setGlobalActionsListener(this);
+        }
+    }
+
+    private void ensureLegacyCreated() {
+        if (mLegacyGlobalActions != null) return;
+        mLegacyGlobalActions = new LegacyGlobalActions(mContext, mWindowManagerFuncs,
+                this::onGlobalActionsDismissed);
     }
 
     public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
@@ -56,6 +66,7 @@
             mHandler.postDelayed(mShowTimeout, 5000);
         } else {
             // SysUI isn't alive, show legacy menu.
+            ensureLegacyCreated();
             mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
         }
     }
@@ -79,6 +90,7 @@
         mStatusBarConnected = connected;
         if (mShowing && !mStatusBarConnected) {
             // Status bar died but we need to be showing global actions still, show the legacy.
+            ensureLegacyCreated();
             mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
         }
     }
@@ -88,6 +100,7 @@
         public void run() {
             if (DEBUG) Slog.d(TAG, "Global actions timeout");
             // We haven't heard from sysui, show the legacy dialog.
+            ensureLegacyCreated();
             mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
         }
     };
diff --git a/services/core/java/com/android/server/security/KeyChainSystemService.java b/services/core/java/com/android/server/security/KeyChainSystemService.java
new file mode 100644
index 0000000..bfeaee6
--- /dev/null
+++ b/services/core/java/com/android/server/security/KeyChainSystemService.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.security.IKeyChainService;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+/**
+ * Service related to {@link android.security.KeyChain}.
+ * <p>
+ * Most of the implementation of KeyChain is provided by the com.android.keychain app. Until O,
+ * this was OK because a system app has roughly the same privileges as the system process.
+ * <p>
+ * With the introduction of background check, PACKAGE_* broadcasts (_ADDED, _REMOVED, _REPLACED)
+ * aren't received when the KeyChain app is in the background, which is bad as it uses those to
+ * drive internal cleanup.
+ * <p>
+ * TODO (b/35968281): take a more sophisticated look at what bits of KeyChain should be inside the
+ *                    system server and which make sense inside a system app.
+ */
+public class KeyChainSystemService extends SystemService {
+
+    private static final String TAG = "KeyChainSystemService";
+
+    public KeyChainSystemService(final Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        IntentFilter packageFilter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+        packageFilter.addDataScheme("package");
+        try {
+            getContext().registerReceiverAsUser(mPackageReceiver, UserHandle.ALL,
+                    packageFilter, null /*broadcastPermission*/, null /*handler*/);
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Unable to register for package removed broadcast", e);
+        }
+    }
+
+    private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(final Context context, final Intent broadcastIntent) {
+            if (broadcastIntent.getPackage() != null) {
+                return;
+            }
+
+            try {
+                final Intent intent = new Intent(IKeyChainService.class.getName());
+                ComponentName service =
+                        intent.resolveSystemService(getContext().getPackageManager(), 0 /*flags*/);
+                if (service == null) {
+                    return;
+                }
+                intent.setComponent(service);
+                intent.setAction(broadcastIntent.getAction());
+                getContext().startServiceAsUser(intent, UserHandle.of(getSendingUserId()));
+            } catch (RuntimeException e) {
+                Slog.e(TAG, "Unable to forward package removed broadcast to KeyChain", e);
+            }
+        }
+    };
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ce28cba..f0732dd 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -99,6 +99,7 @@
 import com.android.server.restrictions.RestrictionsManagerService;
 import com.android.server.retaildemo.RetailDemoModeService;
 import com.android.server.security.KeyAttestationApplicationIdProviderService;
+import com.android.server.security.KeyChainSystemService;
 import com.android.server.soundtrigger.SoundTriggerService;
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
@@ -736,6 +737,10 @@
                     new KeyAttestationApplicationIdProviderService(context));
             traceEnd();
 
+            traceBeginAndSlog("StartKeyChainSystemService");
+            mSystemServiceManager.startService(KeyChainSystemService.class);
+            traceEnd();
+
             traceBeginAndSlog("StartSchedulingPolicyService");
             ServiceManager.addService("scheduling_policy", new SchedulingPolicyService());
             traceEnd();
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 ab83b9d..4c23d79 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -52,6 +52,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import com.android.server.lights.Light;
@@ -259,6 +260,7 @@
 
     @Test
     @UiThreadTest
+    @Ignore("Flaky")
     public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
         mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null).getNotification(), new int[1], 0);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index ca37631..1aa952cd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -34,6 +34,7 @@
 import android.hardware.soundtrigger.SoundTrigger.SoundModel;
 import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
 import android.hardware.soundtrigger.SoundTriggerModule;
+import android.os.DeadObjectException;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.telephony.PhoneStateListener;
@@ -46,6 +47,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 import java.util.UUID;
 
 /**
@@ -376,7 +379,7 @@
 
             ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
             if (modelData == null || !modelData.isKeyphraseModel()) {
-                Slog.e(TAG, "No model exists for given keyphrase Id.");
+                Slog.e(TAG, "No model exists for given keyphrase Id " + keyphraseId);
                 return STATUS_ERROR;
             }
 
@@ -609,13 +612,16 @@
             return;
         }
 
+        model.setStopped();
         try {
             callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
+        } catch (DeadObjectException e) {
+            forceStopAndUnloadModel(model, e);
+            return;
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException in onGenericSoundTriggerDetected", e);
         }
 
-        model.setStopped();
         RecognitionConfig config = model.getRecognitionConfig();
         if (config == null) {
             Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " +
@@ -699,6 +705,8 @@
             modelData.setStopped();
             try {
                 modelData.getCallback().onRecognitionPaused();
+            } catch (DeadObjectException e) {
+                forceStopAndUnloadModel(modelData, e);
             } catch (RemoteException e) {
                 Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
             }
@@ -710,8 +718,6 @@
         MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
         try {
             sendErrorCallbacksToAll(STATUS_ERROR);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "RemoteException in onError", e);
         } finally {
             internalClearModelStateLocked();
             internalClearGlobalStateLocked();
@@ -748,15 +754,17 @@
             Slog.w(TAG, "Received onRecognition event without callback for keyphrase model.");
             return;
         }
+        modelData.setStopped();
 
         try {
             modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
+        } catch (DeadObjectException e) {
+            forceStopAndUnloadModel(modelData, e);
+            return;
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException in onKeyphraseDetected", e);
         }
 
-        modelData.setStopped();
-
         RecognitionConfig config = modelData.getRecognitionConfig();
         if (config != null) {
             // Whether we should continue by starting this again.
@@ -791,10 +799,8 @@
 
     private void onServiceDiedLocked() {
         try {
-          MetricsLogger.count(mContext, "sth_service_died", 1);
+            MetricsLogger.count(mContext, "sth_service_died", 1);
             sendErrorCallbacksToAll(SoundTrigger.STATUS_DEAD_OBJECT);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "RemoteException in onError", e);
         } finally {
             internalClearModelStateLocked();
             internalClearGlobalStateLocked();
@@ -879,11 +885,48 @@
     }
 
     // Sends an error callback to all models with a valid registered callback.
-    private void sendErrorCallbacksToAll(int errorCode) throws RemoteException {
+    private void sendErrorCallbacksToAll(int errorCode) {
         for (ModelData modelData : mModelDataMap.values()) {
             IRecognitionStatusCallback callback = modelData.getCallback();
             if (callback != null) {
-                callback.onError(STATUS_ERROR);
+                try {
+                    callback.onError(errorCode);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException sendErrorCallbacksToAll for model handle " +
+                            modelData.getHandle(), e);
+                }
+            }
+        }
+    }
+
+    private void forceStopAndUnloadModel(ModelData modelData, Exception exception) {
+        if (exception != null) {
+          Slog.e(TAG, "forceStopAndUnloadModel", exception);
+        }
+        if (modelData.isModelStarted()) {
+            Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle());
+            if (mModule.stopRecognition(modelData.getHandle()) != STATUS_OK) {
+                modelData.setStopped();
+                modelData.setRequested(false);
+            } else {
+                Slog.e(TAG, "Failed to stop model " + modelData.getHandle());
+            }
+        }
+        if (modelData.isModelLoaded()) {
+            Slog.d(TAG, "Unloading previously loaded dangling model " + modelData.getHandle());
+            if (mModule.unloadSoundModel(modelData.getHandle()) == STATUS_OK) {
+                // Remove the model data from existence.
+                mModelDataMap.remove(modelData.getModelId());
+                Iterator it = mKeyphraseUuidMap.entrySet().iterator();
+                while (it.hasNext()) {
+                    Map.Entry pair = (Map.Entry) it.next();
+                    if (pair.getValue().equals(modelData.getModelId())) {
+                        it.remove();
+                    }
+                }
+                modelData.clearState();
+            } else {
+                Slog.e(TAG, "Failed to unload model " + modelData.getHandle());
             }
         }
     }
@@ -976,6 +1019,8 @@
             if (notify) {
                 try {
                     callback.onError(status);
+                } catch (DeadObjectException e) {
+                    forceStopAndUnloadModel(modelData, e);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "RemoteException in onError", e);
                 }
@@ -988,6 +1033,8 @@
             if (notify) {
                 try {
                     callback.onRecognitionResumed();
+                } catch (DeadObjectException e) {
+                    forceStopAndUnloadModel(modelData, e);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
                 }
@@ -1013,6 +1060,8 @@
             if (notify) {
                 try {
                     callback.onError(status);
+                } catch (DeadObjectException e) {
+                    forceStopAndUnloadModel(modelData, e);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "RemoteException in onError", e);
                 }
@@ -1024,6 +1073,8 @@
             if (notify) {
                 try {
                     callback.onRecognitionPaused();
+                } catch (DeadObjectException e) {
+                    forceStopAndUnloadModel(modelData, e);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
                 }
diff --git a/tests/testables/src/android/testing/AndroidTestingRunner.java b/tests/testables/src/android/testing/AndroidTestingRunner.java
index 816ed03..a425f70 100644
--- a/tests/testables/src/android/testing/AndroidTestingRunner.java
+++ b/tests/testables/src/android/testing/AndroidTestingRunner.java
@@ -18,7 +18,7 @@
 import android.support.test.internal.runner.junit4.statement.RunBefores;
 import android.support.test.internal.runner.junit4.statement.UiThreadStatement;
 
-import android.testing.TestableLooper.LooperStatement;
+import android.testing.TestableLooper.LooperFrameworkMethod;
 import android.testing.TestableLooper.RunWithLooper;
 
 import org.junit.After;
@@ -30,6 +30,7 @@
 import org.junit.runners.model.InitializationError;
 import org.junit.runners.model.Statement;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -49,28 +50,21 @@
 
     @Override
     protected Statement methodInvoker(FrameworkMethod method, Object test) {
-        return shouldRunOnUiThread(method) ? new UiThreadStatement(
-                methodInvokerInt(method, test), true) : methodInvokerInt(method, test);
-    }
-
-    protected Statement methodInvokerInt(FrameworkMethod method, Object test) {
-        RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
-        if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
-        if (annotation != null) {
-            return new LooperStatement(super.methodInvoker(method, test),
-                    annotation.setAsMainLooper(), test);
-        }
-        return super.methodInvoker(method, test);
+        method = looperWrap(method, test, method);
+        final Statement statement = super.methodInvoker(method, test);
+        return shouldRunOnUiThread(method) ? new UiThreadStatement(statement, true) : statement;
     }
 
     protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
-        List befores = this.getTestClass().getAnnotatedMethods(Before.class);
+        List befores = looperWrap(method, target,
+                this.getTestClass().getAnnotatedMethods(Before.class));
         return befores.isEmpty() ? statement : new RunBefores(method, statement,
                 befores, target);
     }
 
     protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) {
-        List afters = this.getTestClass().getAnnotatedMethods(After.class);
+        List afters = looperWrap(method, target,
+                this.getTestClass().getAnnotatedMethods(After.class));
         return afters.isEmpty() ? statement : new RunAfters(method, statement, afters,
                 target);
     }
@@ -88,6 +82,30 @@
         return annotation == null ? 0L : annotation.timeout();
     }
 
+    protected List<FrameworkMethod> looperWrap(FrameworkMethod method, Object test,
+            List<FrameworkMethod> methods) {
+        RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
+        if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
+        if (annotation != null) {
+            methods = new ArrayList<>(methods);
+            for (int i = 0; i < methods.size(); i++) {
+                methods.set(i, LooperFrameworkMethod.get(methods.get(i),
+                        annotation.setAsMainLooper(), test));
+            }
+        }
+        return methods;
+    }
+
+    protected FrameworkMethod looperWrap(FrameworkMethod method, Object test,
+            FrameworkMethod base) {
+        RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
+        if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
+        if (annotation != null) {
+            return LooperFrameworkMethod.get(base, annotation.setAsMainLooper(), test);
+        }
+        return base;
+    }
+
     public boolean shouldRunOnUiThread(FrameworkMethod method) {
         if (mKlass.getAnnotation(UiThreadTest.class) != null) {
             return true;
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 8a33cf9..62490bc 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -15,20 +15,21 @@
 package android.testing;
 
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
+import android.os.TestLooperManager;
+import android.support.test.InstrumentationRegistry;
 import android.util.ArrayMap;
 
-import org.junit.runners.model.Statement;
+import org.junit.runners.model.FrameworkMethod;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
-import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
-import java.lang.reflect.Method;
 import java.util.Map;
 
 /**
@@ -38,65 +39,35 @@
  */
 public class TestableLooper {
 
-    private final Method mNext;
-    private final Method mRecycleUnchecked;
-
     private Looper mLooper;
     private MessageQueue mQueue;
     private boolean mMain;
     private Object mOriginalMain;
     private MessageHandler mMessageHandler;
 
-    private int mParsedCount;
     private Handler mHandler;
     private Message mEmptyMessage;
+    private TestLooperManager mQueueWrapper;
 
-    public TestableLooper() throws Exception {
-        this(true);
+    public TestableLooper(Looper l) throws Exception {
+        this(InstrumentationRegistry.getInstrumentation().acquireLooperManager(l), l);
     }
 
-    public TestableLooper(boolean setMyLooper) throws Exception {
-        setupQueue(setMyLooper);
-        mNext = mQueue.getClass().getDeclaredMethod("next");
-        mNext.setAccessible(true);
-        mRecycleUnchecked = Message.class.getDeclaredMethod("recycleUnchecked");
-        mRecycleUnchecked.setAccessible(true);
+    private TestableLooper(TestLooperManager wrapper, Looper l) throws Exception {
+        mQueueWrapper = wrapper;
+        setupQueue(l);
+    }
+
+    private TestableLooper(Looper looper, boolean b) throws Exception {
+        setupQueue(looper);
     }
 
     public Looper getLooper() {
         return mLooper;
     }
 
-    private void clearLooper() throws NoSuchFieldException, IllegalAccessException {
-        Field field = Looper.class.getDeclaredField("sThreadLocal");
-        field.setAccessible(true);
-        ThreadLocal<Looper> sThreadLocal = (ThreadLocal<Looper>) field.get(null);
-        sThreadLocal.set(null);
-    }
-
-    private boolean setForCurrentThread() throws NoSuchFieldException, IllegalAccessException {
-        if (Looper.myLooper() != mLooper) {
-            Field field = Looper.class.getDeclaredField("sThreadLocal");
-            field.setAccessible(true);
-            ThreadLocal<Looper> sThreadLocal = (ThreadLocal<Looper>) field.get(null);
-            sThreadLocal.set(mLooper);
-            return true;
-        }
-        return false;
-    }
-
-    private void setupQueue(boolean setMyLooper) throws Exception {
-        if (setMyLooper) {
-            clearLooper();
-            Looper.prepare();
-            mLooper = Looper.myLooper();
-        } else {
-            Constructor<Looper> constructor = Looper.class.getDeclaredConstructor(
-                    boolean.class);
-            constructor.setAccessible(true);
-            mLooper = constructor.newInstance(true);
-        }
-
+    private void setupQueue(Looper l) throws Exception {
+        mLooper = l;
         mQueue = mLooper.getQueue();
         mHandler = new Handler(mLooper);
     }
@@ -121,9 +92,7 @@
      * tests.
      */
     public void destroy() throws NoSuchFieldException, IllegalAccessException {
-        if (Looper.myLooper() == mLooper) {
-            clearLooper();
-        }
+        mQueueWrapper.release();
         if (mMain && mOriginalMain != null) {
             Field field = mLooper.getClass().getDeclaredField("sMainLooper");
             field.setAccessible(true);
@@ -164,26 +133,26 @@
 
     private boolean parseMessageInt() {
         try {
-            Message result = (Message) mNext.invoke(mQueue);
+            Message result = mQueueWrapper.next();
             if (result != null) {
                 // This is a break message.
                 if (result == mEmptyMessage) {
-                    mRecycleUnchecked.invoke(result);
+                    mQueueWrapper.recycle(result);
                     return false;
                 }
 
                 if (mMessageHandler != null) {
                     if (mMessageHandler.onMessageHandled(result)) {
                         result.getTarget().dispatchMessage(result);
-                        mRecycleUnchecked.invoke(result);
+                        mQueueWrapper.recycle(result);
                     } else {
-                        mRecycleUnchecked.invoke(result);
+                        mQueueWrapper.recycle(result);
                         // Message handler indicated it doesn't want us to continue.
                         return false;
                     }
                 } else {
                     result.getTarget().dispatchMessage(result);
-                    mRecycleUnchecked.invoke(result);
+                    mQueueWrapper.recycle(result);
                 }
             } else {
                 // No messages, don't continue parsing
@@ -199,10 +168,14 @@
      * Runs an executable with myLooper set and processes all messages added.
      */
     public void runWithLooper(RunnableWithException runnable) throws Exception {
-        boolean set = setForCurrentThread();
-        runnable.run();
+        new Handler(getLooper()).post(() -> {
+            try {
+                runnable.run();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
         processAllMessages();
-        if (set) clearLooper();
     }
 
     public interface RunnableWithException {
@@ -221,33 +194,131 @@
         return sLoopers.get(test);
     }
 
-    public static class LooperStatement extends Statement {
-        private final boolean mSetAsMain;
-        private final Statement mBase;
-        private final TestableLooper mLooper;
+    public static class LooperFrameworkMethod extends FrameworkMethod {
+        private HandlerThread mHandlerThread;
 
-        public LooperStatement(Statement base, boolean setAsMain, Object test) {
-            mBase = base;
+        private final TestableLooper mTestableLooper;
+        private final Looper mLooper;
+        private final Handler mHandler;
+
+        public LooperFrameworkMethod(FrameworkMethod base, boolean setAsMain, Object test) {
+            super(base.getMethod());
             try {
-                mLooper = new TestableLooper(false);
-                sLoopers.put(test, mLooper);
-                mSetAsMain = setAsMain;
+                mLooper = setAsMain ? Looper.getMainLooper() : createLooper();
+                mTestableLooper = new TestableLooper(mLooper, false);
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
+            sLoopers.put(test, mTestableLooper);
+            mHandler = new Handler(mLooper);
+        }
+
+        public LooperFrameworkMethod(TestableLooper other, FrameworkMethod base) {
+            super(base.getMethod());
+            mLooper = other.mLooper;
+            mTestableLooper = other;
+            mHandler = new Handler(mLooper);
+        }
+
+        public static FrameworkMethod get(FrameworkMethod base, boolean setAsMain, Object test) {
+            if (sLoopers.containsKey(test)) {
+                return new LooperFrameworkMethod(sLoopers.get(test), base);
+            }
+            return new LooperFrameworkMethod(base, setAsMain, test);
         }
 
         @Override
-        public void evaluate() throws Throwable {
-            mLooper.setForCurrentThread();
-            if (mSetAsMain) {
-                mLooper.setAsMainLooper();
+        public Object invokeExplosively(Object target, Object... params) throws Throwable {
+            if (Looper.myLooper() == mLooper) {
+                // Already on the right thread from another statement, just execute then.
+                return super.invokeExplosively(target, params);
+            }
+            boolean set = mTestableLooper.mQueueWrapper == null;
+            if (set) {
+                mTestableLooper.mQueueWrapper = InstrumentationRegistry.getInstrumentation()
+                        .acquireLooperManager(mLooper);
+            }
+            try {
+                Object[] ret = new Object[1];
+                // Run the execution on the looper thread.
+                Runnable execute = () -> {
+                    try {
+                        ret[0] = super.invokeExplosively(target, params);
+                    } catch (Throwable throwable) {
+                        throw new LooperException(throwable);
+                    }
+                };
+                mHandler.post(execute);
+                // Try to wait for the message to be queued.
+                for (int i = 0; i < 10; i++) {
+                    if (!mTestableLooper.mQueueWrapper.hasMessages(mHandler, null, execute)) {
+                        Thread.sleep(1);
+                    }
+                }
+                if (!mTestableLooper.mQueueWrapper.hasMessages(mHandler, null, execute)) {
+                    throw new RuntimeException("Message didn't queue...");
+                }
+                Message m = mTestableLooper.mQueueWrapper.next();
+                // Parse all other messages until we get to ours.
+                while (m.getTarget() != mHandler) {
+                    try {
+                        mTestableLooper.mQueueWrapper.execute(m);
+                    } catch (LooperException e) {
+                        throw e.getSource();
+                    } finally {
+                        mTestableLooper.mQueueWrapper.recycle(m);
+                    }
+                    m = mTestableLooper.mQueueWrapper.next();
+                }
+                // Dispatch our message.
+                try {
+                    mTestableLooper.mQueueWrapper.execute(m);
+                } catch (LooperException e) {
+                    throw e.getSource();
+                } catch (RuntimeException re) {
+                    // If the TestLooperManager has to post, it will wrap what it throws in a
+                    // RuntimeException, make sure we grab the actual source.
+                    if (re.getCause() instanceof LooperException) {
+                        throw ((LooperException) re.getCause()).getSource();
+                    } else {
+                        throw re.getCause();
+                    }
+                } finally {
+                    mTestableLooper.mQueueWrapper.recycle(m);
+                }
+                return ret[0];
+            } finally {
+                if (set) {
+                    mTestableLooper.mQueueWrapper.release();
+                    mTestableLooper.mQueueWrapper = null;
+                }
+            }
+        }
+
+        private Looper createLooper() {
+            // TODO: Find way to share these.
+            mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName());
+            mHandlerThread.start();
+            return mHandlerThread.getLooper();
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            super.finalize();
+            if (mHandlerThread != null) {
+                mHandlerThread.quit();
+            }
+        }
+
+        private static class LooperException extends RuntimeException {
+            private final Throwable mSource;
+
+            public LooperException(Throwable t) {
+                mSource = t;
             }
 
-            try {
-                mBase.evaluate();
-            } finally {
-                mLooper.destroy();
+            public Throwable getSource() {
+                return mSource;
             }
         }
     }
diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index 18e5fff..12f1d0a 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -24,17 +24,16 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.testing.TestableLooper.MessageHandler;
 import android.testing.TestableLooper.RunWithLooper;
 
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 public class TestableLooperTest {
@@ -46,11 +45,6 @@
         mTestableLooper = TestableLooper.get(this);
     }
 
-    @After
-    public void tearDown() throws Exception {
-        mTestableLooper.destroy();
-    }
-
     @Test
     public void testMessageExecuted() throws Exception {
         Handler h = new Handler();
@@ -133,39 +127,23 @@
     @Test
     public void testMainLooper() throws Exception {
         assertNotEquals(Looper.myLooper(), Looper.getMainLooper());
-
-        Looper originalMain = Looper.getMainLooper();
-        mTestableLooper.setAsMainLooper();
-        assertEquals(Looper.myLooper(), Looper.getMainLooper());
-        Runnable r = mock(Runnable.class);
-
-        new Handler(Looper.getMainLooper()).post(r);
-        mTestableLooper.processAllMessages();
-
-        verify(r).run();
-        mTestableLooper.destroy();
-
-        assertEquals(originalMain, Looper.getMainLooper());
-    }
-
-    @Test
-    public void testNotMyLooper() throws Exception {
-        TestableLooper looper = new TestableLooper(false);
-
-        assertEquals(Looper.myLooper(), mTestableLooper.getLooper());
-        assertNotEquals(Looper.myLooper(), looper.getLooper());
-
         Runnable r = mock(Runnable.class);
         Runnable r2 = mock(Runnable.class);
-        new Handler().post(r);
-        new Handler(looper.getLooper()).post(r2);
+        TestableLooper testableLooper = new TestableLooper(Looper.getMainLooper());
 
-        looper.processAllMessages();
-        verify(r2).run();
-        verify(r, never()).run();
+        try {
+            testableLooper.setMessageHandler(m -> {
+                if (m.getCallback() == r) return true;
+                return false;
+            });
+            new Handler(Looper.getMainLooper()).post(r);
+            testableLooper.processAllMessages();
 
-        mTestableLooper.processAllMessages();
-        verify(r).run();
+            verify(r).run();
+            verify(r2, never()).run();
+        } finally {
+            testableLooper.destroy();
+        }
     }
 
     @Test
diff --git a/wifi/java/android/net/wifi/hotspot2/ConfigParser.java b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
index 027b049a..e8e8731 100644
--- a/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
@@ -111,6 +111,7 @@
      *
      * Content-Type: multipart/mixed; boundary={boundary}
      * Content-Transfer-Encoding: base64
+     * [Skip uninterested headers]
      *
      * --{boundary}
      * Content-Type: application/x-passpoint-profile
@@ -326,7 +327,8 @@
                     header.encodingType = entry.getValue();
                     break;
                 default:
-                    throw new IOException("Unexpected header: " + entry.getKey());
+                    Log.d(TAG, "Ignore header: " + entry.getKey());
+                    break;
             }
         }
         return header;
@@ -344,21 +346,24 @@
      * @throws IOException
      */
     private static Pair<String, String> parseContentType(String contentType) throws IOException {
-        String[] attributes = contentType.toString().split(";");
+        String[] attributes = contentType.split(";");
         String type = null;
         String boundary = null;
 
-        if (attributes.length < 1 || attributes.length > 2) {
+        if (attributes.length < 1) {
             throw new IOException("Invalid Content-Type: " + contentType);
         }
 
+        // The type is always the first attribute.
         type = attributes[0].trim();
-        if (attributes.length == 2) {
-            boundary = attributes[1].trim();
-            if (!boundary.startsWith(BOUNDARY)) {
-                throw new IOException("Invalid Content-Type: " + contentType);
+        // Look for boundary string from the rest of the attributes.
+        for (int i = 1; i < attributes.length; i++) {
+            String attribute = attributes[i].trim();
+            if (!attribute.startsWith(BOUNDARY)) {
+                Log.d(TAG, "Ignore Content-Type attribute: " + attributes[i]);
+                continue;
             }
-            boundary = boundary.substring(BOUNDARY.length());
+            boundary = attribute.substring(BOUNDARY.length());
             // Remove the leading and trailing quote if present.
             if (boundary.length() > 1 && boundary.startsWith("\"") && boundary.endsWith("\"")) {
                 boundary = boundary.substring(1, boundary.length()-1);
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java
index 2ffe428..5dc5d13 100644
--- a/wifi/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java
@@ -144,6 +144,8 @@
     private static final String NODE_TIME_LIMIT = "TimeLimit";
     private static final String NODE_USAGE_TIME_PERIOD = "UsageTimePeriod";
     private static final String NODE_CREDENTIAL_PRIORITY = "CredentialPriority";
+    private static final String NODE_EXTENSION = "Extension";
+
     /**
      * Fields under HomeSP subtree.
      */
@@ -629,6 +631,10 @@
                 case NODE_CREDENTIAL_PRIORITY:
                     config.setCredentialPriority(parseInteger(getPpsNodeValue(child)));
                     break;
+                case NODE_EXTENSION:
+                    // All vendor specific information will be under this node.
+                    Log.d(TAG, "Ignore Extension node for vendor specific information");
+                    break;
                 default:
                     throw new ParsingException("Unknown node: " + child.getName());
             }
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
index 995963d..56919c2 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
@@ -1,85 +1,86 @@
-Q29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5PXtib3VuZGFyeX0KQ29udGVu
-dC1UcmFuc2Zlci1FbmNvZGluZzogYmFzZTY0CgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBh
-cHBsaWNhdGlvbi94LXBhc3Nwb2ludC1wcm9maWxlCkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6
-IGJhc2U2NAoKUEUxbmJYUlVjbVZsSUhodGJHNXpQU0p6ZVc1amJXdzZaRzFrWkdZeExqSWlQZ29n
-SUR4V1pYSkVWRVErTVM0eVBDOVdaWEpFVkVRKwpDaUFnUEU1dlpHVStDaUFnSUNBOFRtOWtaVTVo
-YldVK1VHVnlVSEp2ZG1sa1pYSlRkV0p6WTNKcGNIUnBiMjQ4TDA1dlpHVk9ZVzFsClBnb2dJQ0Fn
-UEZKVVVISnZjR1Z5ZEdsbGN6NEtJQ0FnSUNBZ1BGUjVjR1UrQ2lBZ0lDQWdJQ0FnUEVSRVJrNWhi
-V1UrZFhKdU9uZG0KWVRwdGJ6cG9iM1J6Y0c5ME1tUnZkREF0Y0dWeWNISnZkbWxrWlhKemRXSnpZ
-M0pwY0hScGIyNDZNUzR3UEM5RVJFWk9ZVzFsUGdvZwpJQ0FnSUNBOEwxUjVjR1UrQ2lBZ0lDQThM
-MUpVVUhKdmNHVnlkR2xsY3o0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoCmJXVSth
-VEF3TVR3dlRtOWtaVTVoYldVK0NpQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFRt
-RnRaVDVJYjIxbFUxQTgKTDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lD
-QWdJQ0FnUEU1dlpHVk9ZVzFsUGtaeWFXVnVaR3g1VG1GdApaVHd2VG05a1pVNWhiV1UrQ2lBZ0lD
-QWdJQ0FnSUNBOFZtRnNkV1UrUTJWdWRIVnllU0JJYjNWelpUd3ZWbUZzZFdVK0NpQWdJQ0FnCklD
-QWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcx
-bFBrWlJSRTQ4TDA1dlpHVk8KWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBtMXBOaTVqYnk1
-MWF6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZwpJQ0FnSUNBZ0lEeE9iMlJsUGdv
-Z0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsSnZZVzFwYm1kRGIyNXpiM0owYVhWdFQwazhMMDV2
-ClpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsUGpFeE1qSXpNeXcwTkRVMU5qWThMMVpo
-YkhWbFBnb2dJQ0FnSUNBZ0lEd3YKVG05a1pUNEtJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0E4
-VG05a1pUNEtJQ0FnSUNBZ0lDQThUbTlrWlU1aGJXVStRM0psWkdWdQpkR2xoYkR3dlRtOWtaVTVo
-YldVK0NpQWdJQ0FnSUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVTVoYldVK1VtVmhi
-RzA4CkwwNXZaR1ZPWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBuTm9ZV3RsYmk1emRHbHlj
-bVZrTG1OdmJUd3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9i
-MlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsVnpaWEp1WVcxbApVR0Z6YzNkdmNtUThM
-MDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2Iy
-UmxUbUZ0ClpUNVZjMlZ5Ym1GdFpUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
-eDFaVDVxWVcxbGN6d3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lD
-QWdJQ0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxCaApjM04zYjNKa1BD
-OU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbGx0T1hWYVJFRjNUbmM5UFR3
-dlZtRnNkV1UrCkNpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThUbTlrWlQ0
-S0lDQWdJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWwKUGtWQlVFMWxkR2h2WkR3dlRtOWtaVTVoYldV
-K0NpQWdJQ0FnSUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBZ0lDQWdJQ0FnSUR4TwpiMlJsVG1G
-dFpUNUZRVkJVZVhCbFBDOU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnSUNBOFZtRnNkV1Ur
-TWpFOEwxWmhiSFZsClBnb2dJQ0FnSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lDQWdJQ0Fn
-SUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnSUNBZ0lEeE8KYjJSbFRtRnRaVDVKYm01bGNrMWxkR2h2
-WkR3dlRtOWtaVTVoYldVK0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQazFUTFVOSQpRVkF0
-VmpJOEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThM
-MDV2WkdVK0NpQWdJQ0FnCklDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJ
-Q0FnSUNBZ1BFNXZaR1ZPWVcxbFBrUnBaMmwwWVd4RFpYSjAKYVdacFkyRjBaVHd2VG05a1pVNWhi
-V1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbApQ
-a05sY25ScFptbGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
-eDFaVDU0TlRBNWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lD
-QWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1q
-VTJSbWx1WjJWeWNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
-KwpNV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpG
-bU1XWXhaakZtTVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNB
-OEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNB
-Z0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxOSlRUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0E4
-VG05awpaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrbE5VMGs4TDA1dlpHVk9ZVzFs
-UGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzCmRXVSthVzF6YVR3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0Fn
-SUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWcKSUNBZ0lDQWdQRTV2
-WkdWT1lXMWxQa1ZCVUZSNWNHVThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnSUNBOFZtRnNk
-V1UrTWpROApMMVpoYkhWbFBnb2dJQ0FnSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEd3ZU
-bTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nCklDQWdQQzlPYjJSbFBnb2dJRHd2VG05a1pUNEtQ
-QzlOWjIxMFZISmxaVDRLCgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94
-LXg1MDktY2EtY2VydApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQKCkxTMHRMUzFD
-UlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJMUkVORFFXaERaMEYzU1VKQlowbEtR
-VWxNYkVaa2QzcE0KVm5WeVRVRXdSME5UY1VkVFNXSXpSRkZGUWtOM1ZVRk5Ra2w0UlVSQlQwSm5U
-bFlLUWtGTlZFSXdWa0pWUTBKRVVWUkZkMGhvWTA1TgpWRmwzVFZSRmVVMVVSVEZOUkVVeFYyaGpU
-azFxV1hkTlZFRTFUVlJGTVUxRVJURlhha0ZUVFZKQmR3cEVaMWxFVmxGUlJFVjNaRVpSClZrRm5V
-VEJGZUUxSlNVSkpha0ZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVDBOQlVUaEJUVWxKUWtOblMw
-TkJVVVZCQ25wdVFWQlYKZWpJMlRYTmhaVFIzY3pRelkzcFNOREV2U2pKUmRISlRTVnBWUzIxV1ZY
-TldkVzFFWWxsSWNsQk9kbFJZUzFOTldFRmpaWGRQVWtSUgpXVmdLVW5GMlNIWndiamhEYzJOQ01T
-dHZSMWhhZGtoM2VHbzBlbFl3VjB0dlN6SjZaVmhyWVhVemRtTjViRE5JU1V0MWNFcG1jVEpVClJV
-RkRaV1pXYW1vd2RBcEtWeXRZTXpWUVIxZHdPUzlJTlhwSlZVNVdUbFpxVXpkVmJYTTRORWwyUzJo
-U1FqZzFNVEpRUWpsVmVVaGgKWjFoWlZsZzFSMWR3UVdOV2NIbG1jbXhTQ2taSk9WRmthR2dyVUdK
-ck1IVjVhM1JrWW1ZdlEyUm1aMGhQYjJWaWNsUjBkMUpzYWswdwpiMFIwV0NzeVEzWTJhakIzUWtz
-M2FFUTRjRkIyWmpFcmRYa0tSM3BqZW1sblFWVXZORXQzTjJWYWNYbGtaamxDS3pWU2RYQlNLMGxh
-CmFYQllOREY0UldsSmNrdFNkM0ZwTlRFM1YxZDZXR05xWVVjeVkwNWlaalExTVFwNGNFZzFVRzVX
-TTJreGRIRXdOR3BOUjFGVmVrWjMKU1VSQlVVRkNielJIUVUxSU5IZElVVmxFVmxJd1QwSkNXVVZH
-U1hkWU5IWnpPRUpwUW1OVFkyOWtDalZ1YjFwSVVrMDRSVFFyYVUxRgpTVWRCTVZWa1NYZFJOMDFF
-YlVGR1NYZFlOSFp6T0VKcFFtTlRZMjlrTlc1dldraFNUVGhGTkN0cGIxSmhhMFpFUVZNS1RWSkJk
-MFJuCldVUldVVkZFUlhka1JsRldRV2RSTUVWNFoyZHJRV2QxVlZZelJFMTBWelp6ZDBSQldVUldV
-akJVUWtGVmQwRjNSVUl2ZWtGTVFtZE8KVmdwSVVUaEZRa0ZOUTBGUldYZEVVVmxLUzI5YVNXaDJZ
-MDVCVVVWTVFsRkJSR2RuUlVKQlJtWlJjVTlVUVRkU2RqZExLMngxVVRkdwpibUZ6TkVKWmQwaEZD
-amxIUlZBdmRXOW9kalpMVDNrd1ZFZFJSbUp5VWxScVJtOU1WazVDT1VKYU1YbHRUVVJhTUM5VVNY
-ZEpWV00zCmQyazNZVGgwTlcxRmNWbElNVFV6ZDFjS1lWZHZiMmxUYW5sTVRHaDFTVFJ6VG5KT1Ew
-OTBhWE5rUW5FeWNqSk5SbGgwTm1nd2JVRlIKV1U5UWRqaFNPRXMzTDJablUzaEhSbkY2YUhsT2JX
-MVdUQW94Y1VKS2JHUjRNelJUY0hkelZFRk1VVlpRWWpSb1IzZEtlbHBtY2pGUQpZM0JGVVhnMmVF
-MXVWR3c0ZUVWWFdrVXpUWE01T1hWaFZYaGlVWEZKZDFKMUNreG5RVTlyVGtOdFdUSnRPRGxXYUhw
-aFNFb3hkVlk0Ck5VRmtUUzkwUkN0WmMyMXNibTVxZERsTVVrTmxhbUpDYVhCcVNVZHFUMWh5WnpG
-S1VDdHNlRllLYlhWTk5IWklLMUF2Yld4dGVITlEKVUhvd1pEWTFZaXRGUjIxS1duQnZUR3RQTDNS
-a1RrNTJRMWw2YWtwd1ZFVlhjRVZ6VHpaT1RXaExXVzg5Q2kwdExTMHRSVTVFSUVORgpVbFJKUmts
-RFFWUkZMUzB0TFMwSwotLXtib3VuZGFyeX0tLQo=
+TUlNRS1WZXJzaW9uOiAxLjAKQ29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5
+PXtib3VuZGFyeX07IGNoYXJzZXQ9VVRGLTgKQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmFz
+ZTY0CgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXBhc3Nwb2ludC1w
+cm9maWxlOyBjaGFyc2V0PVVURi04CkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IGJhc2U2NAoK
+UEUxbmJYUlVjbVZsSUhodGJHNXpQU0p6ZVc1amJXdzZaRzFrWkdZeExqSWlQZ29nSUR4V1pYSkVW
+RVErTVM0eVBDOVdaWEpFVkVRKwpDaUFnUEU1dlpHVStDaUFnSUNBOFRtOWtaVTVoYldVK1VHVnlV
+SEp2ZG1sa1pYSlRkV0p6WTNKcGNIUnBiMjQ4TDA1dlpHVk9ZVzFsClBnb2dJQ0FnUEZKVVVISnZj
+R1Z5ZEdsbGN6NEtJQ0FnSUNBZ1BGUjVjR1UrQ2lBZ0lDQWdJQ0FnUEVSRVJrNWhiV1UrZFhKdU9u
+ZG0KWVRwdGJ6cG9iM1J6Y0c5ME1tUnZkREF0Y0dWeWNISnZkbWxrWlhKemRXSnpZM0pwY0hScGIy
+NDZNUzR3UEM5RVJFWk9ZVzFsUGdvZwpJQ0FnSUNBOEwxUjVjR1UrQ2lBZ0lDQThMMUpVVUhKdmNH
+VnlkR2xsY3o0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoCmJXVSthVEF3TVR3dlRt
+OWtaVTVoYldVK0NpQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFRtRnRaVDVJYjIx
+bFUxQTgKTDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnUEU1
+dlpHVk9ZVzFsUGtaeWFXVnVaR3g1VG1GdApaVHd2VG05a1pVNWhiV1UrQ2lBZ0lDQWdJQ0FnSUNB
+OFZtRnNkV1UrUTJWdWRIVnllU0JJYjNWelpUd3ZWbUZzZFdVK0NpQWdJQ0FnCklDQWdQQzlPYjJS
+bFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrWlJSRTQ4
+TDA1dlpHVk8KWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBtMXBOaTVqYnk1MWF6d3ZWbUZz
+ZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZwpJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0Fn
+SUNBZ1BFNXZaR1ZPWVcxbFBsSnZZVzFwYm1kRGIyNXpiM0owYVhWdFQwazhMMDV2ClpHVk9ZVzFs
+UGdvZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsUGpFeE1qSXpNeXcwTkRVMU5qWThMMVpoYkhWbFBnb2dJ
+Q0FnSUNBZ0lEd3YKVG05a1pUNEtJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0E4VG05a1pUNEtJ
+Q0FnSUNBZ0lDQThUbTlrWlU1aGJXVStRM0psWkdWdQpkR2xoYkR3dlRtOWtaVTVoYldVK0NpQWdJ
+Q0FnSUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVTVoYldVK1VtVmhiRzA4CkwwNXZa
+R1ZPWVcxbFBnb2dJQ0FnSUNBZ0lDQWdQRlpoYkhWbFBuTm9ZV3RsYmk1emRHbHljbVZrTG1OdmJU
+d3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lD
+QWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsVnpaWEp1WVcxbApVR0Z6YzNkdmNtUThMMDV2WkdWT1lX
+MWxQZ29nSUNBZ0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmxUbUZ0ClpU
+NVZjMlZ5Ym1GdFpUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lXeDFaVDVxWVcx
+bGN6d3ZWbUZzZFdVK0NpQWcKSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lDQWdJQ0E4VG05
+a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxCaApjM04zYjNKa1BDOU9iMlJsVG1G
+dFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbGx0T1hWYVJFRjNUbmM5UFR3dlZtRnNkV1Ur
+CkNpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThUbTlrWlQ0S0lDQWdJQ0Fn
+SUNBZ0lDQWdQRTV2WkdWT1lXMWwKUGtWQlVFMWxkR2h2WkR3dlRtOWtaVTVoYldVK0NpQWdJQ0Fn
+SUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBZ0lDQWdJQ0FnSUR4TwpiMlJsVG1GdFpUNUZRVkJV
+ZVhCbFBDOU9iMlJsVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnSUNBOFZtRnNkV1UrTWpFOEwxWmhi
+SFZsClBnb2dJQ0FnSUNBZ0lDQWdJQ0E4TDA1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmxQ
+Z29nSUNBZ0lDQWdJQ0FnSUNBZ0lEeE8KYjJSbFRtRnRaVDVKYm01bGNrMWxkR2h2WkR3dlRtOWta
+VTVoYldVK0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQazFUTFVOSQpRVkF0VmpJOEwxWmhi
+SFZsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0Np
+QWdJQ0FnCklDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEeE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BF
+NXZaR1ZPWVcxbFBrUnBaMmwwWVd4RFpYSjAKYVdacFkyRjBaVHd2VG05a1pVNWhiV1UrQ2lBZ0lD
+QWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbApQa05sY25ScFpt
+bGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lXeDFaVDU0TlRB
+NWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnUEU1
+dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1qVTJSbWx1WjJW
+eWNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdVKwpNV1l4WmpG
+bU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZt
+TVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNBOEwwNXZaR1Ur
+Q2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0Fn
+UEU1dlpHVk9ZVzFsUGxOSlRUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0E4VG05awpaVDRL
+SUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrbE5VMGs4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJ
+Q0FnSUNBZ0lDQThWbUZzCmRXVSthVzF6YVR3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNBOEwwNXZa
+R1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWcKSUNBZ0lDQWdQRTV2WkdWT1lXMWxQ
+a1ZCVUZSNWNHVThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnSUNBOFZtRnNkV1UrTWpROApM
+MVpoYkhWbFBnb2dJQ0FnSUNBZ0lDQWdQQzlPYjJSbFBnb2dJQ0FnSUNBZ0lEd3ZUbTlrWlQ0S0lD
+QWdJQ0FnUEM5T2IyUmxQZ29nCklDQWdQQzlPYjJSbFBnb2dJRHd2VG05a1pUNEtQQzlOWjIxMFZI
+SmxaVDRLCgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXg1MDktY2Et
+Y2VydApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQKCkxTMHRMUzFDUlVkSlRpQkRS
+VkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJMUkVORFFXaERaMEYzU1VKQlowbEtRVWxNYkVaa2Qz
+cE0KVm5WeVRVRXdSME5UY1VkVFNXSXpSRkZGUWtOM1ZVRk5Ra2w0UlVSQlQwSm5UbFlLUWtGTlZF
+SXdWa0pWUTBKRVVWUkZkMGhvWTA1TgpWRmwzVFZSRmVVMVVSVEZOUkVVeFYyaGpUazFxV1hkTlZF
+RTFUVlJGTVUxRVJURlhha0ZUVFZKQmR3cEVaMWxFVmxGUlJFVjNaRVpSClZrRm5VVEJGZUUxSlNV
+Skpha0ZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVDBOQlVUaEJUVWxKUWtOblMwTkJVVVZCQ25w
+dVFWQlYKZWpJMlRYTmhaVFIzY3pRelkzcFNOREV2U2pKUmRISlRTVnBWUzIxV1ZYTldkVzFFWWxs
+SWNsQk9kbFJZUzFOTldFRmpaWGRQVWtSUgpXVmdLVW5GMlNIWndiamhEYzJOQ01TdHZSMWhhZGto
+M2VHbzBlbFl3VjB0dlN6SjZaVmhyWVhVemRtTjViRE5JU1V0MWNFcG1jVEpVClJVRkRaV1pXYW1v
+d2RBcEtWeXRZTXpWUVIxZHdPUzlJTlhwSlZVNVdUbFpxVXpkVmJYTTRORWwyUzJoU1FqZzFNVEpR
+UWpsVmVVaGgKWjFoWlZsZzFSMWR3UVdOV2NIbG1jbXhTQ2taSk9WRmthR2dyVUdKck1IVjVhM1Jr
+WW1ZdlEyUm1aMGhQYjJWaWNsUjBkMUpzYWswdwpiMFIwV0NzeVEzWTJhakIzUWtzM2FFUTRjRkIy
+WmpFcmRYa0tSM3BqZW1sblFWVXZORXQzTjJWYWNYbGtaamxDS3pWU2RYQlNLMGxhCmFYQllOREY0
+UldsSmNrdFNkM0ZwTlRFM1YxZDZXR05xWVVjeVkwNWlaalExTVFwNGNFZzFVRzVXTTJreGRIRXdO
+R3BOUjFGVmVrWjMKU1VSQlVVRkNielJIUVUxSU5IZElVVmxFVmxJd1QwSkNXVVZHU1hkWU5IWnpP
+RUpwUW1OVFkyOWtDalZ1YjFwSVVrMDRSVFFyYVUxRgpTVWRCTVZWa1NYZFJOMDFFYlVGR1NYZFlO
+SFp6T0VKcFFtTlRZMjlrTlc1dldraFNUVGhGTkN0cGIxSmhhMFpFUVZNS1RWSkJkMFJuCldVUldV
+VkZFUlhka1JsRldRV2RSTUVWNFoyZHJRV2QxVlZZelJFMTBWelp6ZDBSQldVUldVakJVUWtGVmQw
+RjNSVUl2ZWtGTVFtZE8KVmdwSVVUaEZRa0ZOUTBGUldYZEVVVmxLUzI5YVNXaDJZMDVCVVVWTVFs
+RkJSR2RuUlVKQlJtWlJjVTlVUVRkU2RqZExLMngxVVRkdwpibUZ6TkVKWmQwaEZDamxIUlZBdmRX
+OW9kalpMVDNrd1ZFZFJSbUp5VWxScVJtOU1WazVDT1VKYU1YbHRUVVJhTUM5VVNYZEpWV00zCmQy
+azNZVGgwTlcxRmNWbElNVFV6ZDFjS1lWZHZiMmxUYW5sTVRHaDFTVFJ6VG5KT1EwOTBhWE5rUW5F
+eWNqSk5SbGgwTm1nd2JVRlIKV1U5UWRqaFNPRXMzTDJablUzaEhSbkY2YUhsT2JXMVdUQW94Y1VK
+S2JHUjRNelJUY0hkelZFRk1VVlpRWWpSb1IzZEtlbHBtY2pGUQpZM0JGVVhnMmVFMXVWR3c0ZUVW
+WFdrVXpUWE01T1hWaFZYaGlVWEZKZDFKMUNreG5RVTlyVGtOdFdUSnRPRGxXYUhwaFNFb3hkVlk0
+Ck5VRmtUUzkwUkN0WmMyMXNibTVxZERsTVVrTmxhbUpDYVhCcVNVZHFUMWh5WnpGS1VDdHNlRllL
+YlhWTk5IWklLMUF2Yld4dGVITlEKVUhvd1pEWTFZaXRGUjIxS1duQnZUR3RQTDNSa1RrNTJRMWw2
+YWtwd1ZFVlhjRVZ6VHpaT1RXaExXVzg5Q2kwdExTMHRSVTVFSUVORgpVbFJKUmtsRFFWUkZMUzB0
+TFMwSwotLXtib3VuZGFyeX0tLQo=
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
index 3ddd09f..a44b542 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
@@ -1,8 +1,9 @@
-Content-Type: multipart/mixed; boundary={boundary}
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary={boundary}; charset=UTF-8
 Content-Transfer-Encoding: base64
 
 --{boundary}
-Content-Type: application/x-passpoint-profile
+Content-Type: application/x-passpoint-profile; charset=UTF-8
 Content-Transfer-Encoding: base64
 
 PE1nbXRUcmVlIHhtbG5zPSJzeW5jbWw6ZG1kZGYxLjIiPgogIDxWZXJEVEQ+MS4yPC9WZXJEVEQ+
diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml
index 7f2d95d..1fb8309 100644
--- a/wifi/tests/assets/pps/PerProviderSubscription.xml
+++ b/wifi/tests/assets/pps/PerProviderSubscription.xml
@@ -14,6 +14,13 @@
     <Node>
       <NodeName>i001</NodeName>
       <Node>
+        <NodeName>Extension</NodeName>
+        <Node>
+          <NodeName>VendorSpecific</NodeName>
+          <Value>Test</Value>
+        </Node>
+      </Node>
+      <Node>
         <NodeName>HomeSP</NodeName>
         <Node>
           <NodeName>FriendlyName</NodeName>