Merge "Removed hidden mutable APIs from PorterDuffColorFilter"
diff --git a/Android.bp b/Android.bp
index 9daeb37..a84632a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -853,6 +853,8 @@
     "core/tests/utiltests/jni",
 ]
 
+// TODO(b/77285514): remove this once the last few hidl interfaces have been
+// updated to use hwbinder.stubs.
 java_library {
     name: "hwbinder",
     no_framework_libs: true,
@@ -1072,3 +1074,34 @@
     exact_api_filename: "test-exact.txt",
     args: framework_docs_args + " -referenceonly -showAnnotation android.annotation.TestApi -nodocs",
 }
+
+droiddoc {
+    name: "hwbinder-stubs-docs",
+    srcs: [
+        "core/java/android/os/HidlSupport.java",
+        "core/java/android/annotation/IntDef.java",
+        "core/java/android/annotation/NonNull.java",
+        "core/java/android/annotation/SystemApi.java",
+        "core/java/android/os/HwBinder.java",
+        "core/java/android/os/HwBlob.java",
+        "core/java/android/os/HwParcel.java",
+        "core/java/android/os/IHwBinder.java",
+        "core/java/android/os/IHwInterface.java",
+        "core/java/android/os/DeadObjectException.java",
+        "core/java/android/os/DeadSystemException.java",
+        "core/java/android/os/RemoteException.java",
+        "core/java/android/util/AndroidException.java",
+    ],
+    custom_template: "droiddoc-templates-sdk",
+    installable: false,
+    no_framework_libs: true,
+    args: "-showAnnotation android.annotation.SystemApi -nodocs -stubsourceonly",
+}
+
+java_library_static {
+    name: "hwbinder.stubs",
+    sdk_version: "core_current",
+    srcs: [
+        ":hwbinder-stubs-docs",
+    ],
+}
diff --git a/api/current.txt b/api/current.txt
index e5b3aee..4363b27 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7206,8 +7206,6 @@
 
   public final class Slice implements android.os.Parcelable {
     ctor protected Slice(android.os.Parcel);
-    method public static deprecated android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
-    method public static deprecated android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent, java.util.List<android.app.slice.SliceSpec>);
     method public int describeContents();
     method public java.util.List<java.lang.String> getHints();
     method public java.util.List<android.app.slice.SliceItem> getItems();
@@ -7297,15 +7295,17 @@
   }
 
   public class SliceManager {
-    method public android.app.slice.Slice bindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
-    method public android.app.slice.Slice bindSlice(android.content.Intent, java.util.List<android.app.slice.SliceSpec>);
+    method public android.app.slice.Slice bindSlice(android.net.Uri, java.util.Set<android.app.slice.SliceSpec>);
+    method public deprecated android.app.slice.Slice bindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
+    method public android.app.slice.Slice bindSlice(android.content.Intent, java.util.Set<android.app.slice.SliceSpec>);
     method public int checkSlicePermission(android.net.Uri, int, int);
     method public java.util.List<android.net.Uri> getPinnedSlices();
-    method public java.util.List<android.app.slice.SliceSpec> getPinnedSpecs(android.net.Uri);
+    method public java.util.Set<android.app.slice.SliceSpec> getPinnedSpecs(android.net.Uri);
     method public java.util.Collection<android.net.Uri> getSliceDescendants(android.net.Uri);
     method public void grantSlicePermission(java.lang.String, android.net.Uri);
     method public android.net.Uri mapIntentToUri(android.content.Intent);
-    method public void pinSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
+    method public void pinSlice(android.net.Uri, java.util.Set<android.app.slice.SliceSpec>);
+    method public deprecated void pinSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
     method public void revokeSlicePermission(java.lang.String, android.net.Uri);
     method public void unpinSlice(android.net.Uri);
     field public static final java.lang.String CATEGORY_SLICE = "android.app.slice.category.SLICE";
@@ -7325,7 +7325,8 @@
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public final java.lang.String getType(android.net.Uri);
     method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
-    method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
+    method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.Set<android.app.slice.SliceSpec>);
+    method public deprecated android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
     method public android.app.PendingIntent onCreatePermissionRequest(android.net.Uri);
     method public java.util.Collection<android.net.Uri> onGetSliceDescendants(android.net.Uri);
     method public android.net.Uri onMapIntentToUri(android.content.Intent);
diff --git a/api/system-current.txt b/api/system-current.txt
index 6186a55..a559b7a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1027,7 +1027,6 @@
     method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
     method public abstract int getIntentVerificationStatusAsUser(java.lang.String, int);
     method public abstract int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle);
-    method public android.os.PersistableBundle getSuspendedPackageAppExtras(java.lang.String);
     method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
     method public abstract int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -1039,7 +1038,6 @@
     method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
     method public void setHarmfulAppWarning(java.lang.String, java.lang.CharSequence);
     method public java.lang.String[] setPackagesSuspended(java.lang.String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, java.lang.String);
-    method public void setSuspendedPackageAppExtras(java.lang.String, android.os.PersistableBundle);
     method public abstract void setUpdateAvailable(java.lang.String, boolean);
     method public abstract boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
     method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
diff --git a/api/test-current.txt b/api/test-current.txt
index d5b1399..77f56a4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -752,6 +752,14 @@
     field public static final int ID_TYPE_SERIAL = 1; // 0x1
   }
 
+  public final class KeyProtection implements java.security.KeyStore.ProtectionParameter {
+    method public long getBoundToSpecificSecureUserId();
+  }
+
+  public static final class KeyProtection.Builder {
+    method public android.security.keystore.KeyProtection.Builder setBoundToSpecificSecureUserId(long);
+  }
+
 }
 
 package android.service.autofill {
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 27bbc4b..2e93d88 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2165,30 +2165,18 @@
     }
 
     @Override
-    public PersistableBundle getSuspendedPackageAppExtras(String packageName) {
+    public Bundle getSuspendedPackageAppExtras() {
+        final PersistableBundle extras;
         try {
-            return mPM.getSuspendedPackageAppExtras(packageName, mContext.getUserId());
+            extras = mPM.getSuspendedPackageAppExtras(mContext.getOpPackageName(),
+                    mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-    }
-
-    @Override
-    public Bundle getSuspendedPackageAppExtras() {
-        final PersistableBundle extras = getSuspendedPackageAppExtras(mContext.getOpPackageName());
         return extras != null ? new Bundle(extras.deepCopy()) : null;
     }
 
     @Override
-    public void setSuspendedPackageAppExtras(String packageName, PersistableBundle appExtras) {
-        try {
-            mPM.setSuspendedPackageAppExtras(packageName, appExtras, mContext.getUserId());
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-    }
-
-    @Override
     public boolean isPackageSuspendedForUser(String packageName, int userId) {
         try {
             return mPM.isPackageSuspendedForUser(packageName, userId);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 4a7cf62..9e47ced 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -328,7 +328,8 @@
      * Group information is only used for presentation, not for behavior.
      *
      * Only modifiable before the channel is submitted to
-     * {@link NotificationManager#notify(String, int, Notification)}.
+     * {@link NotificationManager#createNotificationChannel(NotificationChannel)}, unless the
+     * channel is not currently part of a group.
      *
      * @param groupId the id of a group created by
      * {@link NotificationManager#createNotificationChannelGroup(NotificationChannelGroup)}.
@@ -341,6 +342,9 @@
      * Sets whether notifications posted to this channel can appear as application icon badges
      * in a Launcher.
      *
+     * Only modifiable before the channel is submitted to
+     * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
+     *
      * @param showBadge true if badges should be allowed to be shown.
      */
     public void setShowBadge(boolean showBadge) {
@@ -353,7 +357,7 @@
      * least {@link NotificationManager#IMPORTANCE_DEFAULT} should have a sound.
      *
      * Only modifiable before the channel is submitted to
-     * {@link NotificationManager#notify(String, int, Notification)}.
+     * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
      */
     public void setSound(Uri sound, AudioAttributes audioAttributes) {
         this.mSound = sound;
@@ -365,7 +369,7 @@
      * on devices that support that feature.
      *
      * Only modifiable before the channel is submitted to
-     * {@link NotificationManager#notify(String, int, Notification)}.
+     * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
      */
     public void enableLights(boolean lights) {
         this.mLights = lights;
@@ -376,7 +380,7 @@
      * {@link #enableLights(boolean) enabled} on this channel and the device supports that feature.
      *
      * Only modifiable before the channel is submitted to
-     * {@link NotificationManager#notify(String, int, Notification)}.
+     * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
      */
     public void setLightColor(int argb) {
         this.mLightColor = argb;
@@ -387,7 +391,7 @@
      * be set with {@link #setVibrationPattern(long[])}.
      *
      * Only modifiable before the channel is submitted to
-     * {@link NotificationManager#notify(String, int, Notification)}.
+     * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
      */
     public void enableVibration(boolean vibration) {
         this.mVibrationEnabled = vibration;
@@ -399,7 +403,7 @@
      * vibration} as well. Otherwise, vibration will be disabled.
      *
      * Only modifiable before the channel is submitted to
-     * {@link NotificationManager#notify(String, int, Notification)}.
+     * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
      */
     public void setVibrationPattern(long[] vibrationPattern) {
         this.mVibrationEnabled = vibrationPattern != null && vibrationPattern.length > 0;
@@ -407,9 +411,10 @@
     }
 
     /**
-     * Sets the level of interruption of this notification channel. Only
-     * modifiable before the channel is submitted to
-     * {@link NotificationManager#notify(String, int, Notification)}.
+     * Sets the level of interruption of this notification channel.
+     *
+     * Only modifiable before the channel is submitted to
+     * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
      *
      * @param importance the amount the user should be interrupted by
      *            notifications from this channel.
diff --git a/core/java/android/app/slice/ISliceManager.aidl b/core/java/android/app/slice/ISliceManager.aidl
index a2aaf12..69852f3 100644
--- a/core/java/android/app/slice/ISliceManager.aidl
+++ b/core/java/android/app/slice/ISliceManager.aidl
@@ -25,11 +25,15 @@
     void unpinSlice(String pkg, in Uri uri, in IBinder token);
     boolean hasSliceAccess(String pkg);
     SliceSpec[] getPinnedSpecs(in Uri uri, String pkg);
-    int checkSlicePermission(in Uri uri, String pkg, int pid, int uid,
-            in String[] autoGrantPermissions);
-    void grantPermissionFromUser(in Uri uri, String pkg, String callingPkg, boolean allSlices);
     Uri[] getPinnedSlices(String pkg);
 
     byte[] getBackupPayload(int user);
     void applyRestore(in byte[] payload, int user);
+
+    // Perms.
+    void grantSlicePermission(String callingPkg, String toPkg, in Uri uri);
+    void revokeSlicePermission(String callingPkg, String toPkg, in Uri uri);
+    int checkSlicePermission(in Uri uri, String pkg, int pid, int uid,
+            in String[] autoGrantPermissions);
+    void grantPermissionFromUser(in Uri uri, String pkg, String callingPkg, boolean allSlices);
 }
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index bf3398a..4336f18 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -21,19 +21,13 @@
 import android.annotation.StringDef;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.IContentProvider;
-import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.RemoteException;
 
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -575,45 +569,4 @@
         }
         return sb.toString();
     }
-
-    /**
-     * @deprecated TO BE REMOVED.
-     */
-    @Deprecated
-    public static @Nullable Slice bindSlice(ContentResolver resolver,
-            @NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) {
-        Preconditions.checkNotNull(uri, "uri");
-        IContentProvider provider = resolver.acquireProvider(uri);
-        if (provider == null) {
-            throw new IllegalArgumentException("Unknown URI " + uri);
-        }
-        try {
-            Bundle extras = new Bundle();
-            extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
-            extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
-                    new ArrayList<>(supportedSpecs));
-            final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
-                    null, extras);
-            Bundle.setDefusable(res, true);
-            if (res == null) {
-                return null;
-            }
-            return res.getParcelable(SliceProvider.EXTRA_SLICE);
-        } catch (RemoteException e) {
-            // Arbitrary and not worth documenting, as Activity
-            // Manager will kill this process shortly anyway.
-            return null;
-        } finally {
-            resolver.releaseProvider(provider);
-        }
-    }
-
-    /**
-     * @deprecated TO BE REMOVED.
-     */
-    @Deprecated
-    public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent,
-            @NonNull List<SliceSpec> supportedSpecs) {
-        return context.getSystemService(SliceManager.class).bindSlice(intent, supportedSpecs);
-    }
 }
diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java
index 0285e9f..dc09f12 100644
--- a/core/java/android/app/slice/SliceManager.java
+++ b/core/java/android/app/slice/SliceManager.java
@@ -16,6 +16,8 @@
 
 package android.app.slice;
 
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
@@ -38,6 +40,7 @@
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.util.Preconditions;
@@ -47,6 +50,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Class to handle interactions with {@link Slice}s.
@@ -101,22 +105,6 @@
     private final IBinder mToken = new Binder();
 
     /**
-     * Permission denied.
-     * @hide
-     */
-    public static final int PERMISSION_DENIED = -1;
-    /**
-     * Permission granted.
-     * @hide
-     */
-    public static final int PERMISSION_GRANTED = 0;
-    /**
-     * Permission just granted by the user, and should be granted uri permission as well.
-     * @hide
-     */
-    public static final int PERMISSION_USER_GRANTED = 1;
-
-    /**
      * @hide
      */
     public SliceManager(Context context, Handler handler) throws ServiceNotFoundException {
@@ -140,7 +128,7 @@
      * @see Intent#ACTION_ASSIST
      * @see Intent#CATEGORY_HOME
      */
-    public void pinSlice(@NonNull Uri uri, @NonNull List<SliceSpec> specs) {
+    public void pinSlice(@NonNull Uri uri, @NonNull Set<SliceSpec> specs) {
         try {
             mService.pinSlice(mContext.getPackageName(), uri,
                     specs.toArray(new SliceSpec[specs.size()]), mToken);
@@ -150,6 +138,14 @@
     }
 
     /**
+     * @deprecated TO BE REMOVED
+     */
+    @Deprecated
+    public void pinSlice(@NonNull Uri uri, @NonNull List<SliceSpec> specs) {
+        pinSlice(uri, new ArraySet<>(specs));
+    }
+
+    /**
      * Remove a pin for a slice.
      * <p>
      * If the slice has no other pins/callbacks then the slice will be unpinned.
@@ -189,9 +185,10 @@
      * into account all clients and returns only specs supported by all.
      * @see SliceSpec
      */
-    public @NonNull List<SliceSpec> getPinnedSpecs(Uri uri) {
+    public @NonNull Set<SliceSpec> getPinnedSpecs(Uri uri) {
         try {
-            return Arrays.asList(mService.getPinnedSpecs(uri, mContext.getPackageName()));
+            return new ArraySet<>(Arrays.asList(mService.getPinnedSpecs(uri,
+                    mContext.getPackageName())));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -240,7 +237,7 @@
      * @return The Slice provided by the app or null if none is given.
      * @see Slice
      */
-    public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) {
+    public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull Set<SliceSpec> supportedSpecs) {
         Preconditions.checkNotNull(uri, "uri");
         ContentResolver resolver = mContext.getContentResolver();
         try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) {
@@ -265,6 +262,14 @@
     }
 
     /**
+     * @deprecated TO BE REMOVED
+     */
+    @Deprecated
+    public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) {
+        return bindSlice(uri, new ArraySet<>(supportedSpecs));
+    }
+
+    /**
      * Turns a slice intent into a slice uri. Expects an explicit intent.
      * <p>
      * This goes through a several stage resolution process to determine if any slice
@@ -351,7 +356,7 @@
      * @see Intent
      */
     public @Nullable Slice bindSlice(@NonNull Intent intent,
-            @NonNull List<SliceSpec> supportedSpecs) {
+            @NonNull Set<SliceSpec> supportedSpecs) {
         Preconditions.checkNotNull(intent, "intent");
         Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null
                 || intent.getData() != null,
@@ -417,9 +422,11 @@
      * @see #grantSlicePermission(String, Uri)
      */
     public @PermissionResult int checkSlicePermission(@NonNull Uri uri, int pid, int uid) {
-        // TODO: Switch off Uri permissions.
-        return mContext.checkUriPermission(uri, pid, uid,
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        try {
+            return mService.checkSlicePermission(uri, null, pid, uid, null);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -431,11 +438,11 @@
      * @see #revokeSlicePermission
      */
     public void grantSlicePermission(@NonNull String toPackage, @NonNull Uri uri) {
-        // TODO: Switch off Uri permissions.
-        mContext.grantUriPermission(toPackage, uri,
-                Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
-                        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                        | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+        try {
+            mService.grantSlicePermission(mContext.getPackageName(), toPackage, uri);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -453,11 +460,11 @@
      * @see #grantSlicePermission
      */
     public void revokeSlicePermission(@NonNull String toPackage, @NonNull Uri uri) {
-        // TODO: Switch off Uri permissions.
-        mContext.revokeUriPermission(toPackage, uri,
-                Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
-                        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                        | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+        try {
+            mService.revokeSlicePermission(mContext.getPackageName(), toPackage, uri);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -478,16 +485,6 @@
                 throw new SecurityException("User " + uid + " does not have slice permission for "
                         + uri + ".");
             }
-            if (result == PERMISSION_USER_GRANTED) {
-                // We just had a user grant of this permission and need to grant this to the app
-                // permanently.
-                mContext.grantUriPermission(pkg, uri.buildUpon().path("").build(),
-                        Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
-                                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
-                // Notify a change has happened because we just granted a permission.
-                mContext.getContentResolver().notifyChange(uri, null);
-            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index fe5742d..d369272 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -44,6 +44,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 /**
  * A SliceProvider allows an app to provide content to be displayed in system spaces. This content
@@ -197,6 +198,14 @@
      * @see {@link Slice}.
      * @see {@link Slice#HINT_PARTIAL}
      */
+    public Slice onBindSlice(Uri sliceUri, Set<SliceSpec> supportedSpecs) {
+        return onBindSlice(sliceUri, new ArrayList<>(supportedSpecs));
+    }
+
+    /**
+     * @deprecated TO BE REMOVED
+     */
+    @Deprecated
     public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
         return null;
     }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 02ce47b8..2be33e9 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -280,9 +280,6 @@
 
     PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId);
 
-    void setSuspendedPackageAppExtras(String packageName, in PersistableBundle appExtras,
-            int userId);
-
     /**
      * Backup/restore support - only the system uid may use these.
      */
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6e952c0..6bbcf17 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5622,46 +5622,6 @@
     }
 
     /**
-     * Retrieve the {@link PersistableBundle} that was passed as {@code appExtras} when the given
-     * package was suspended.
-     *
-     * <p> The caller must hold permission {@link Manifest.permission#SUSPEND_APPS} to use this
-     * api.</p>
-     *
-     * @param packageName The package to retrieve extras for.
-     * @return The {@code appExtras} for the suspended package.
-     *
-     * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String)
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(Manifest.permission.SUSPEND_APPS)
-    public @Nullable PersistableBundle getSuspendedPackageAppExtras(String packageName) {
-        throw new UnsupportedOperationException("getSuspendedPackageAppExtras not implemented");
-    }
-
-    /**
-     * Set the app extras for a suspended package. This method can be used to update the appExtras
-     * for a package that was earlier suspended using
-     * {@link #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
-     * String)}
-     * Does nothing if the given package is not already in a suspended state.
-     *
-     * @param packageName The package for which the appExtras need to be updated
-     * @param appExtras The new appExtras for the given package
-     *
-     * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String)
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(Manifest.permission.SUSPEND_APPS)
-    public void setSuspendedPackageAppExtras(String packageName,
-            @Nullable PersistableBundle appExtras) {
-        throw new UnsupportedOperationException("setSuspendedPackageAppExtras not implemented");
-    }
-
-    /**
      * Returns any extra information supplied as {@code appExtras} to the system when the calling
      * app was suspended.
      *
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index 96edfa3..c2c3182 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -303,10 +303,9 @@
      *
      * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
      */
-    public void getBounds(@NonNull Paint paint, @IntRange(from = 0) int start,
-            @IntRange(from = 0) int end, @NonNull Rect bounds) {
-        nGetBounds(mNativePtr, mCopiedBuffer, paint.getNativeInstance(), start, end,
-                paint.getBidiFlags(), bounds);
+    public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
+            @NonNull Rect bounds) {
+        nGetBounds(mNativePtr, mCopiedBuffer, start, end, bounds);
     }
 
     /**
@@ -743,6 +742,6 @@
     @CriticalNative
     private static native int nGetMemoryUsage(/* Non Zero */ long nativePtr);
 
-    private static native void nGetBounds(long nativePtr, char[] buf, long paintPtr, int start,
-            int end, int bidiFlag, Rect rect);
+    private static native void nGetBounds(long nativePtr, char[] buf, int start, int end,
+            Rect rect);
 }
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 44789d6..369f357 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -515,8 +515,7 @@
                 + "para: (" + paraStart + ", " + paraEnd + "), "
                 + "request: (" + start + ", " + end + ")");
         }
-        getMeasuredParagraph(paraIndex).getBounds(mParams.mPaint,
-                start - paraStart, end - paraStart, bounds);
+        getMeasuredParagraph(paraIndex).getBounds(start - paraStart, end - paraStart, bounds);
     }
 
     /**
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index ddba477..634c661 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -34,10 +34,6 @@
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.text.TextUtils.SimpleStringSplitter;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
 import android.util.Pair;
 import android.util.Printer;
 import android.util.Slog;
@@ -783,58 +779,6 @@
     }
 
     /**
-     * Parses the setting stored input methods and subtypes string value.
-     *
-     * @param inputMethodsAndSubtypesString The input method subtypes value stored in settings.
-     * @return Map from input method ID to set of input method subtypes IDs.
-     */
-    @VisibleForTesting
-    public static ArrayMap<String, ArraySet<String>> parseInputMethodsAndSubtypesString(
-            @Nullable final String inputMethodsAndSubtypesString) {
-
-        final ArrayMap<String, ArraySet<String>> imeMap = new ArrayMap<>();
-        if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
-            return imeMap;
-        }
-
-        final SimpleStringSplitter typeSplitter =
-                new SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
-        final SimpleStringSplitter subtypeSplitter =
-                new SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
-
-        List<Pair<String, ArrayList<String>>> allImeSettings =
-                InputMethodSettings.buildInputMethodsAndSubtypeList(inputMethodsAndSubtypesString,
-                        typeSplitter,
-                        subtypeSplitter);
-        for (Pair<String, ArrayList<String>> ime : allImeSettings) {
-            ArraySet<String> subtypes = new ArraySet<>();
-            if (ime.second != null) {
-                subtypes.addAll(ime.second);
-            }
-            imeMap.put(ime.first, subtypes);
-        }
-        return imeMap;
-    }
-
-    @NonNull
-    public static String buildInputMethodsAndSubtypesString(
-            @NonNull final ArrayMap<String, ArraySet<String>> map) {
-        // we want to use the canonical InputMethodSettings implementation,
-        // so we convert data structures first.
-        List<Pair<String, ArrayList<String>>> imeMap = new ArrayList<>(4);
-        for (ArrayMap.Entry<String, ArraySet<String>> entry : map.entrySet()) {
-            final String imeName = entry.getKey();
-            final ArraySet<String> subtypeSet = entry.getValue();
-            final ArrayList<String> subtypes = new ArrayList<>(2);
-            if (subtypeSet != null) {
-                subtypes.addAll(subtypeSet);
-            }
-            imeMap.add(new Pair<>(imeName, subtypes));
-        }
-        return InputMethodSettings.buildInputMethodsSettingString(imeMap);
-    }
-
-    /**
      * Utility class for putting and getting settings for InputMethod
      * TODO: Move all putters and getters of settings to this class.
      */
@@ -871,21 +815,7 @@
             }
         }
 
-        public static String buildInputMethodsSettingString(
-                List<Pair<String, ArrayList<String>>> allImeSettingsMap) {
-            final StringBuilder b = new StringBuilder();
-            boolean needsSeparator = false;
-            for (Pair<String, ArrayList<String>> ime : allImeSettingsMap) {
-                if (needsSeparator) {
-                    b.append(INPUT_METHOD_SEPARATOR);
-                }
-                buildEnabledInputMethodsSettingString(b, ime);
-                needsSeparator = true;
-            }
-            return b.toString();
-        }
-
-        public static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
+        private static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
                 String enabledInputMethodsStr,
                 TextUtils.SimpleStringSplitter inputMethodSplitter,
                 TextUtils.SimpleStringSplitter subtypeSplitter) {
diff --git a/core/jni/android_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp
index 9d79417..41a81ac 100644
--- a/core/jni/android_text_MeasuredParagraph.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -111,20 +111,13 @@
 }
 
 // Regular JNI
-static void nGetBounds(JNIEnv* env, jobject, jlong ptr, jcharArray javaText, jlong paintPtr,
-                           jint start, jint end, jint bidiFlags, jobject bounds) {
+static void nGetBounds(JNIEnv* env, jobject, jlong ptr, jcharArray javaText, jint start, jint end,
+                       jobject bounds) {
     ScopedCharArrayRO text(env, javaText);
     const minikin::U16StringPiece textBuffer(text.get(), text.size());
+    const minikin::Range range(start, end);
 
-    minikin::MeasuredText* mt = toMeasuredParagraph(ptr);
-    Paint* paint = toPaint(paintPtr);
-    const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
-    minikin::Layout layout = MinikinUtils::doLayout(paint,
-            static_cast<minikin::Bidi>(bidiFlags), typeface, textBuffer.data(), start, end - start,
-            textBuffer.size(), mt);
-
-    minikin::MinikinRect rect;
-    layout.getBounds(&rect);
+    minikin::MinikinRect rect = toMeasuredParagraph(ptr)->getBounds(textBuffer, range);
 
     SkRect r;
     r.fLeft = rect.mLeft;
@@ -156,7 +149,7 @@
 
     // MeasuredParagraph native functions.
     {"nGetWidth", "(JII)F", (void*) nGetWidth},  // Critical Natives
-    {"nGetBounds", "(J[CJIIILandroid/graphics/Rect;)V", (void*) nGetBounds},  // Regular JNI
+    {"nGetBounds", "(J[CIILandroid/graphics/Rect;)V", (void*) nGetBounds},  // Regular JNI
     {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc},  // Critical Natives
     {"nGetMemoryUsage", "(J)I", (void*) nGetMemoryUsage},  // Critical Native
 };
diff --git a/core/tests/coretests/src/android/graphics/drawable/DrawableWrapperTest.java b/core/tests/coretests/src/android/graphics/drawable/DrawableWrapperTest.java
new file mode 100644
index 0000000..655efb5
--- /dev/null
+++ b/core/tests/coretests/src/android/graphics/drawable/DrawableWrapperTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Xfermode;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DrawableWrapperTest {
+
+    static class MyWrapper extends DrawableWrapper {
+        MyWrapper(Drawable dr) {
+          super(dr);
+        }
+    }
+
+    /**
+     * Test {@link Drawable#setXfermode(Xfermode)} which is marked
+     * with the hide annotation
+     */
+    @Test
+    public void testSetXfermode() {
+        CacheXfermodeDrawable xferModeDrawable = new CacheXfermodeDrawable();
+        DrawableWrapper wrapper = new MyWrapper(xferModeDrawable);
+        PorterDuffXfermode mode = new PorterDuffXfermode(Mode.MULTIPLY);
+        wrapper.setXfermode(mode);
+        assertSame(xferModeDrawable, wrapper.getDrawable());
+        assertEquals(mode, xferModeDrawable.mXferMode);
+    }
+
+    private static class CacheXfermodeDrawable extends Drawable {
+
+        private Xfermode mXferMode = null;
+
+        @Override
+        public void draw(Canvas canvas) {
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter colorFilter) {
+        }
+
+        @Override
+        public int getOpacity() {
+            return 0;
+        }
+
+        @Override
+        public void setXfermode(Xfermode mode) {
+            mXferMode = mode;
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
index c205f96..6c05601 100644
--- a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
+++ b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
@@ -54,7 +54,7 @@
         first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
 
         // Do not copy NoCopySpan if specified so.
-        final SpannedString copied = new SpannedString(first, false /* copyNoCopySpan */);
+        final SpannedString copied = new SpannedString(first, true /* ignoreNoCopySpan */);
         final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
         assertNotNull(spans);
         assertEquals(2, spans.length);
@@ -87,7 +87,7 @@
 
         // Do not copy NoCopySpan if specified so.
         final SpannedString copied = new SpannedString(
-                new CustomSpannable(first), false /* copyNoCopySpan */);
+                new CustomSpannable(first), true /* ignoreNoCopySpan */);
         final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
         assertNotNull(spans);
         assertEquals(2, spans.length);
diff --git a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
index 0680924..380e315 100644
--- a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
+++ b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
@@ -54,7 +54,7 @@
         first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
 
         // Do not copy NoCopySpan if specified so.
-        final SpannedString copied = new SpannedString(first, false /* copyNoCopySpan */);
+        final SpannedString copied = new SpannedString(first, true /* ignoreNoCopySpan */);
         final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
         assertNotNull(spans);
         assertEquals(2, spans.length);
@@ -87,7 +87,7 @@
 
         // Do not copy NoCopySpan if specified so.
         final SpannedString copied = new SpannedString(
-                new CustomSpanned(first), false /* copyNoCopySpan */);
+                new CustomSpanned(first), true /* ignoreNoCopySpan */);
         final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
         assertNotNull(spans);
         assertEquals(2, spans.length);
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
index 1fd24e3..a70dae8 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
@@ -40,8 +40,6 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -1192,248 +1190,6 @@
     }
 
     @Test
-    public void testParseInputMethodsAndSubtypesString() {
-        // Trivial cases.
-        {
-            assertTrue(InputMethodUtils.parseInputMethodsAndSubtypesString("").isEmpty());
-            assertTrue(InputMethodUtils.parseInputMethodsAndSubtypesString(null).isEmpty());
-        }
-
-        // No subtype cases.
-        {
-            ArrayMap<String, ArraySet<String>> r =
-                    InputMethodUtils.parseInputMethodsAndSubtypesString("ime0");
-            assertEquals(1, r.size());
-            assertTrue(r.containsKey("ime0"));
-            assertTrue(r.get("ime0").isEmpty());
-        }
-        {
-            ArrayMap<String, ArraySet<String>> r =
-                    InputMethodUtils.parseInputMethodsAndSubtypesString("ime0:ime1");
-            assertEquals(2, r.size());
-            assertTrue(r.containsKey("ime0"));
-            assertTrue(r.get("ime0").isEmpty());
-            assertTrue(r.containsKey("ime1"));
-            assertTrue(r.get("ime1").isEmpty());
-        }
-
-        // Input metho IDs and their subtypes.
-        {
-            ArrayMap<String, ArraySet<String>> r =
-                    InputMethodUtils.parseInputMethodsAndSubtypesString("ime0;subtype0");
-            assertEquals(1, r.size());
-            assertTrue(r.containsKey("ime0"));
-            ArraySet<String> subtypes = r.get("ime0");
-            assertEquals(1, subtypes.size());
-            assertTrue(subtypes.contains("subtype0"));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> r =
-                    InputMethodUtils.parseInputMethodsAndSubtypesString("ime0;subtype0;subtype0");
-            assertEquals(1, r.size());
-            assertTrue(r.containsKey("ime0"));
-            ArraySet<String> subtypes = r.get("ime0");
-            assertEquals(1, subtypes.size());
-            assertTrue(subtypes.contains("subtype0"));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> r =
-                    InputMethodUtils.parseInputMethodsAndSubtypesString("ime0;subtype0;subtype1");
-            assertEquals(1, r.size());
-            assertTrue(r.containsKey("ime0"));
-            ArraySet<String> subtypes = r.get("ime0");
-            assertEquals(2, subtypes.size());
-            assertTrue(subtypes.contains("subtype0"));
-            assertTrue(subtypes.contains("subtype1"));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> r =
-                    InputMethodUtils.parseInputMethodsAndSubtypesString(
-                            "ime0;subtype0:ime1;subtype1");
-            assertEquals(2, r.size());
-            assertTrue(r.containsKey("ime0"));
-            assertTrue(r.containsKey("ime1"));
-            ArraySet<String> subtypes0 = r.get("ime0");
-            assertEquals(1, subtypes0.size());
-            assertTrue(subtypes0.contains("subtype0"));
-
-            ArraySet<String> subtypes1 = r.get("ime1");
-            assertEquals(1, subtypes1.size());
-            assertTrue(subtypes1.contains("subtype1"));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> r =
-                    InputMethodUtils.parseInputMethodsAndSubtypesString(
-                            "ime0;subtype0;subtype1:ime1;subtype2");
-            assertEquals(2, r.size());
-            assertTrue(r.containsKey("ime0"));
-            assertTrue(r.containsKey("ime1"));
-            ArraySet<String> subtypes0 = r.get("ime0");
-            assertEquals(2, subtypes0.size());
-            assertTrue(subtypes0.contains("subtype0"));
-            assertTrue(subtypes0.contains("subtype1"));
-
-            ArraySet<String> subtypes1 = r.get("ime1");
-            assertEquals(1, subtypes1.size());
-            assertTrue(subtypes1.contains("subtype2"));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> r =
-                    InputMethodUtils.parseInputMethodsAndSubtypesString(
-                            "ime0;subtype0;subtype1:ime1;subtype1;subtype2");
-            assertEquals(2, r.size());
-            assertTrue(r.containsKey("ime0"));
-            assertTrue(r.containsKey("ime1"));
-            ArraySet<String> subtypes0 = r.get("ime0");
-            assertEquals(2, subtypes0.size());
-            assertTrue(subtypes0.contains("subtype0"));
-            assertTrue(subtypes0.contains("subtype1"));
-
-            ArraySet<String> subtypes1 = r.get("ime1");
-            assertEquals(2, subtypes1.size());
-            assertTrue(subtypes0.contains("subtype1"));
-            assertTrue(subtypes1.contains("subtype2"));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> r =
-                    InputMethodUtils.parseInputMethodsAndSubtypesString(
-                            "ime0;subtype0;subtype1:ime1;subtype1;subtype2:ime2");
-            assertEquals(3, r.size());
-            assertTrue(r.containsKey("ime0"));
-            assertTrue(r.containsKey("ime1"));
-            assertTrue(r.containsKey("ime2"));
-            ArraySet<String> subtypes0 = r.get("ime0");
-            assertEquals(2, subtypes0.size());
-            assertTrue(subtypes0.contains("subtype0"));
-            assertTrue(subtypes0.contains("subtype1"));
-
-            ArraySet<String> subtypes1 = r.get("ime1");
-            assertEquals(2, subtypes1.size());
-            assertTrue(subtypes0.contains("subtype1"));
-            assertTrue(subtypes1.contains("subtype2"));
-
-            ArraySet<String> subtypes2 = r.get("ime2");
-            assertTrue(subtypes2.isEmpty());
-        }
-    }
-
-    @Test
-    public void testbuildInputMethodsAndSubtypesString() {
-        {
-            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
-            assertEquals("", InputMethodUtils.buildInputMethodsAndSubtypesString(map));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
-            map.put("ime0", new ArraySet<>());
-            assertEquals("ime0", InputMethodUtils.buildInputMethodsAndSubtypesString(map));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
-            ArraySet<String> subtypes1 = new ArraySet<>();
-            subtypes1.add("subtype0");
-            map.put("ime0", subtypes1);
-            assertEquals("ime0;subtype0", InputMethodUtils.buildInputMethodsAndSubtypesString(map));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
-            ArraySet<String> subtypes1 = new ArraySet<>();
-            subtypes1.add("subtype0");
-            subtypes1.add("subtype1");
-            map.put("ime0", subtypes1);
-
-            // We do not expect what order will be used to concatenate items in
-            // InputMethodUtils.buildInputMethodsAndSubtypesString() hence enumerate all possible
-            // permutations here.
-            ArraySet<String> validSequences = new ArraySet<>();
-            validSequences.add("ime0;subtype0;subtype1");
-            validSequences.add("ime0;subtype1;subtype0");
-            assertTrue(validSequences.contains(
-                    InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
-            map.put("ime0", new ArraySet<>());
-            map.put("ime1", new ArraySet<>());
-
-            ArraySet<String> validSequences = new ArraySet<>();
-            validSequences.add("ime0:ime1");
-            validSequences.add("ime1:ime0");
-            assertTrue(validSequences.contains(
-                    InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
-            ArraySet<String> subtypes1 = new ArraySet<>();
-            subtypes1.add("subtype0");
-            map.put("ime0", subtypes1);
-            map.put("ime1", new ArraySet<>());
-
-            ArraySet<String> validSequences = new ArraySet<>();
-            validSequences.add("ime0;subtype0:ime1");
-            validSequences.add("ime1;ime0;subtype0");
-            assertTrue(validSequences.contains(
-                    InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
-            ArraySet<String> subtypes1 = new ArraySet<>();
-            subtypes1.add("subtype0");
-            subtypes1.add("subtype1");
-            map.put("ime0", subtypes1);
-            map.put("ime1", new ArraySet<>());
-
-            ArraySet<String> validSequences = new ArraySet<>();
-            validSequences.add("ime0;subtype0;subtype1:ime1");
-            validSequences.add("ime0;subtype1;subtype0:ime1");
-            validSequences.add("ime1:ime0;subtype0;subtype1");
-            validSequences.add("ime1:ime0;subtype1;subtype0");
-            assertTrue(validSequences.contains(
-                    InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
-            ArraySet<String> subtypes1 = new ArraySet<>();
-            subtypes1.add("subtype0");
-            map.put("ime0", subtypes1);
-
-            ArraySet<String> subtypes2 = new ArraySet<>();
-            subtypes2.add("subtype1");
-            map.put("ime1", subtypes2);
-
-            ArraySet<String> validSequences = new ArraySet<>();
-            validSequences.add("ime0;subtype0:ime1;subtype1");
-            validSequences.add("ime1;subtype1:ime0;subtype0");
-            assertTrue(validSequences.contains(
-                    InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
-        }
-        {
-            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
-            ArraySet<String> subtypes1 = new ArraySet<>();
-            subtypes1.add("subtype0");
-            subtypes1.add("subtype1");
-            map.put("ime0", subtypes1);
-
-            ArraySet<String> subtypes2 = new ArraySet<>();
-            subtypes2.add("subtype2");
-            subtypes2.add("subtype3");
-            map.put("ime1", subtypes2);
-
-            ArraySet<String> validSequences = new ArraySet<>();
-            validSequences.add("ime0;subtype0;subtype1:ime1;subtype2;subtype3");
-            validSequences.add("ime0;subtype1;subtype0:ime1;subtype2;subtype3");
-            validSequences.add("ime0;subtype0;subtype1:ime1;subtype3;subtype2");
-            validSequences.add("ime0;subtype1;subtype0:ime1;subtype3;subtype2");
-            validSequences.add("ime1;subtype2;subtype3:ime0;subtype0;subtype1");
-            validSequences.add("ime2;subtype3;subtype2:ime0;subtype0;subtype1");
-            validSequences.add("ime3;subtype2;subtype3:ime0;subtype1;subtype0");
-            validSequences.add("ime4;subtype3;subtype2:ime0;subtype1;subtype0");
-            assertTrue(validSequences.contains(
-                    InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
-        }
-    }
-
-    @Test
     public void testConstructLocaleFromString() throws Exception {
         assertEquals(new Locale("en"), InputMethodUtils.constructLocaleFromString("en"));
         assertEquals(new Locale("en", "US"), InputMethodUtils.constructLocaleFromString("en_US"));
diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
index cf821bb..a9e0f9a 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -16,11 +16,6 @@
 
 package android.graphics.drawable;
 
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.ActivityInfo.Config;
@@ -35,10 +30,16 @@
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
+import android.graphics.Xfermode;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.view.View;
 
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 
 /**
@@ -78,6 +79,16 @@
     }
 
     /**
+     * @hide
+     */
+    @Override
+    public void setXfermode(Xfermode mode) {
+        if (mDrawable != null) {
+            mDrawable.setXfermode(mode);
+        }
+    }
+
+    /**
      * Sets the wrapped drawable.
      *
      * @param dr the wrapped drawable
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index c0d0fb0..d95feb0 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -673,8 +673,8 @@
     }
 
     /**
-     * Returns {@code true} if the screen must be unlocked for this key to be used for encryption or
-     * signing. Decryption and signature verification will still be available when the screen is
+     * Returns {@code true} if the screen must be unlocked for this key to be used for decryption or
+     * signing. Encryption and signature verification will still be available when the screen is
      * locked.
      *
      * @see Builder#setUnlockedDeviceRequired(boolean)
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index 41dc201..92bee8d 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -19,6 +19,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.app.KeyguardManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.security.GateKeeper;
@@ -496,6 +497,7 @@
      * @see KeymasterUtils#addUserAuthArgs
      * @hide
      */
+    @TestApi
     public long getBoundToSpecificSecureUserId() {
         return mBoundToSecureUserId;
     }
@@ -511,8 +513,8 @@
     }
 
     /**
-     * Returns {@code true} if the screen must be unlocked for this key to be used for encryption or
-     * signing. Decryption and signature verification will still be available when the screen is
+     * Returns {@code true} if the screen must be unlocked for this key to be used for decryption or
+     * signing. Encryption and signature verification will still be available when the screen is
      * locked.
      *
      * @see Builder#setUnlockedDeviceRequired(boolean)
@@ -913,6 +915,7 @@
          * @see KeyProtection#getBoundToSpecificSecureUserId()
          * @hide
          */
+        @TestApi
         public Builder setBoundToSpecificSecureUserId(long secureUserId) {
             mBoundToSecureUserId = secureUserId;
             return this;
diff --git a/libs/hwui/debug/gles_decls.in b/libs/hwui/debug/gles_decls.in
index 16574a7..3900959 100644
--- a/libs/hwui/debug/gles_decls.in
+++ b/libs/hwui/debug/gles_decls.in
@@ -387,7 +387,6 @@
 GL_ENTRY(void, glDrawElementsBaseVertexOES, GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex)
 GL_ENTRY(void, glDrawRangeElementsBaseVertexOES, GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex)
 GL_ENTRY(void, glDrawElementsInstancedBaseVertexOES, GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex)
-GL_ENTRY(void, glMultiDrawElementsBaseVertexOES, GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, const GLint *basevertex)
 GL_ENTRY(void, glFramebufferTextureOES, GLenum target, GLenum attachment, GLuint texture, GLint level)
 GL_ENTRY(void, glGetProgramBinaryOES, GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary)
 GL_ENTRY(void, glProgramBinaryOES, GLuint program, GLenum binaryFormat, const void *binary, GLint length)
@@ -541,4 +540,4 @@
 GL_ENTRY(void, glTextureStorage1DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width)
 GL_ENTRY(void, glTextureStorage2DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)
 GL_ENTRY(void, glTextureStorage3DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth)
-GL_ENTRY(void, glTextureViewEXT, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers)
\ No newline at end of file
+GL_ENTRY(void, glTextureViewEXT, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers)
diff --git a/libs/hwui/debug/gles_stubs.in b/libs/hwui/debug/gles_stubs.in
index 4064a39..7cba0c1 100644
--- a/libs/hwui/debug/gles_stubs.in
+++ b/libs/hwui/debug/gles_stubs.in
@@ -1165,9 +1165,6 @@
 void API_ENTRY(glDrawElementsInstancedBaseVertexOES)(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex) {
     CALL_GL_API(glDrawElementsInstancedBaseVertexOES, mode, count, type, indices, instancecount, basevertex);
 }
-void API_ENTRY(glMultiDrawElementsBaseVertexOES)(GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, const GLint *basevertex) {
-    CALL_GL_API(glMultiDrawElementsBaseVertexOES, mode, count, type, indices, primcount, basevertex);
-}
 void API_ENTRY(glFramebufferTextureOES)(GLenum target, GLenum attachment, GLuint texture, GLint level) {
     CALL_GL_API(glFramebufferTextureOES, target, attachment, texture, level);
 }
@@ -1629,4 +1626,4 @@
 }
 void API_ENTRY(glTextureViewEXT)(GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) {
     CALL_GL_API(glTextureViewEXT, texture, target, origtexture, internalformat, minlevel, numlevels, minlayer, numlayers);
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
index e63bdd6..a6c1634 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
@@ -60,7 +60,7 @@
 
     // InputMethods and subtypes are saved in the settings as follows:
     // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
-    private static String buildInputMethodsAndSubtypesString(
+    public static String buildInputMethodsAndSubtypesString(
             final HashMap<String, HashSet<String>> imeToSubtypesMap) {
         final StringBuilder builder = new StringBuilder();
         for (final String imi : imeToSubtypesMap.keySet()) {
@@ -115,7 +115,7 @@
         return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
     }
 
-    private static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
+    public static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
             final String inputMethodsAndSubtypesString) {
         final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
         if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java
new file mode 100644
index 0000000..b4ed3de
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+@RunWith(RobolectricTestRunner.class)
+public class InputMethodAndSubtypeUtilTest {
+
+    private static final HashSet<String> EMPTY_STRING_SET = new HashSet<>();
+
+    private static HashSet<String> asHashSet(String... strings) {
+        HashSet<String> hashSet = new HashSet<>();
+        for (String s : strings) {
+            hashSet.add(s);
+        }
+        return hashSet;
+    }
+
+    @Test
+    public void testParseInputMethodsAndSubtypesString_EmptyString() {
+        assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString("")).isEmpty();
+        assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(null)).isEmpty();
+    }
+
+    @Test
+    public void testParseInputMethodsAndSubtypesString_SingleImeNoSubtype() {
+        HashMap<String, HashSet<String>> r =
+                InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString("ime0");
+        assertThat(r).containsExactly("ime0", EMPTY_STRING_SET);
+    }
+
+    @Test
+    public void testParseInputMethodsAndSubtypesString_MultipleImesNoSubtype() {
+        HashMap<String, HashSet<String>> r =
+                InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString("ime0:ime1");
+        assertThat(r).containsExactly("ime0", EMPTY_STRING_SET, "ime1", EMPTY_STRING_SET);
+    }
+
+    @Test
+    public void testParseInputMethodsAndSubtypesString_SingleImeSingleSubtype() {
+        HashMap<String, HashSet<String>> r =
+                InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString("ime0;subtype0");
+        assertThat(r).containsExactly("ime0", asHashSet("subtype0"));
+    }
+
+    @Test
+    public void testParseInputMethodsAndSubtypesString_SingleImeDuplicateSameSubtypes() {
+        HashMap<String, HashSet<String>> r =
+                InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
+                        "ime0;subtype0;subtype0");
+        assertThat(r).containsExactly("ime0", asHashSet("subtype0"));
+    }
+
+    @Test
+    public void testParseInputMethodsAndSubtypesString_SingleImeMultipleSubtypes() {
+        HashMap<String, HashSet<String>> r =
+                InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
+                        "ime0;subtype0;subtype1");
+        assertThat(r).containsExactly("ime0", asHashSet("subtype0", "subtype1"));
+    }
+
+    @Test
+    public void testParseInputMethodsAndSubtypesString_MultiplePairsOfImeSubtype() {
+        assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
+                "ime0;subtype0:ime1;subtype1"))
+                .containsExactly("ime0", asHashSet("subtype0"), "ime1", asHashSet("subtype1"));
+        assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
+                "ime0;subtype0;subtype1:ime1;subtype2"))
+                .containsExactly("ime0", asHashSet("subtype0", "subtype1"),
+                        "ime1", asHashSet("subtype2"));
+        assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
+                "ime0;subtype0;subtype1:ime1;subtype1;subtype2"))
+                .containsExactly("ime0", asHashSet("subtype0", "subtype1"),
+                        "ime1", asHashSet("subtype1", "subtype2"));
+
+    }
+
+    @Test
+    public void testParseInputMethodsAndSubtypesString_MixedImeSubtypePairsAndImeNoSubtype() {
+        HashMap<String, HashSet<String>> r =
+                InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
+                        "ime0;subtype0;subtype1:ime1;subtype1;subtype2:ime2");
+        assertThat(r).containsExactly("ime0", asHashSet("subtype0", "subtype1"),
+                "ime1", asHashSet("subtype1", "subtype2"),
+                "ime2", EMPTY_STRING_SET);
+    }
+
+    @Test
+    public void testBuildInputMethodsAndSubtypesString_EmptyInput() {
+        HashMap<String, HashSet<String>> map = new HashMap<>();
+        assertThat(map).isEmpty();
+    }
+
+    @Test
+    public void testBuildInputMethodsAndSubtypesString_SingleIme() {
+        HashMap<String, HashSet<String>> map = new HashMap<>();
+        map.put("ime0", new HashSet<>());
+        String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
+        assertThat(result).isEqualTo("ime0");
+    }
+
+    @Test
+    public void testBuildInputMethodsAndSubtypesString_SingleImeSingleSubtype() {
+        HashMap<String, HashSet<String>> map = new HashMap<>();
+        map.put("ime0", asHashSet("subtype0"));
+        String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
+        assertThat(result).isEqualTo("ime0;subtype0");
+    }
+
+    @Test
+    public void testBuildInputMethodsAndSubtypesString_SingleImeMultipleSubtypes() {
+        HashMap<String, HashSet<String>> map = new HashMap<>();
+        map.put("ime0", asHashSet("subtype0", "subtype1"));
+        String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
+
+        // We do not expect what order will be used to concatenate items in
+        // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
+        // permutations here.
+        assertThat(result).matches("(ime0;subtype0;subtype1)|(ime0;subtype1;subtype0)");
+    }
+
+    @Test
+    public void testBuildInputMethodsAndSubtypesString_MultipleImesNoSubtypes() {
+        HashMap<String, HashSet<String>> map = new HashMap<>();
+        map.put("ime0", EMPTY_STRING_SET);
+        map.put("ime1", EMPTY_STRING_SET);
+        String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
+
+        // We do not expect what order will be used to concatenate items in
+        // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
+        // permutations here.
+        assertThat(result).matches("(ime0:ime1)|(ime1:ime0)");
+    }
+
+    @Test
+    public void testBuildInputMethodsAndSubtypesString_MultipleImesWithAndWithoutSubtypes() {
+        HashMap<String, HashSet<String>> map = new HashMap<>();
+        map.put("ime0", asHashSet("subtype0", "subtype1"));
+        map.put("ime1", EMPTY_STRING_SET);
+        String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
+
+        // We do not expect what order will be used to concatenate items in
+        // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
+        // permutations here.
+        assertThat(result).matches("(ime0;subtype0;subtype1:ime1)|(ime0;subtype1;subtype0:ime1)"
+                + "|(ime1:ime0;subtype0;subtype1)|(ime1:ime0;subtype1;subtype0)");
+    }
+
+    @Test
+    public void testBuildInputMethodsAndSubtypesString_MultipleImesWithSubtypes() {
+        HashMap<String, HashSet<String>> map = new HashMap<>();
+        map.put("ime0", asHashSet("subtype0", "subtype1"));
+        map.put("ime1", asHashSet("subtype2", "subtype3"));
+        String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
+
+        // We do not expect what order will be used to concatenate items in
+        // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
+        // permutations here.
+        assertThat(result).matches("|(ime0;subtype0;subtype1:ime1;subtype2;subtype3)|"
+                + "|(ime0;subtype1;subtype0:ime1;subtype2;subtype3)|"
+                + "|(ime0;subtype0;subtype1:ime1;subtype3;subtype2)|"
+                + "|(ime0;subtype1;subtype0:ime1;subtype3;subtype2)|"
+                + "|(ime1;subtype2;subtype3:ime0;subtype0;subtype1)|"
+                + "|(ime2;subtype3;subtype2:ime0;subtype0;subtype1)|"
+                + "|(ime3;subtype2;subtype3:ime0;subtype1;subtype0)|"
+                + "|(ime4;subtype3;subtype2:ime0;subtype1;subtype0)");
+    }
+}
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index addbf84..37de433 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -47,6 +47,8 @@
     <dimen name="widget_title_font_size">24sp</dimen>
     <!-- Slice subtitle  -->
     <dimen name="widget_label_font_size">16sp</dimen>
+    <!-- Slice offset when pulsing -->
+    <dimen name="widget_pulsing_bottom_padding">24dp</dimen>
     <!-- Clock without header -->
     <dimen name="widget_big_font_size">64dp</dimen>
     <!-- Clock with header -->
diff --git a/packages/SystemUI/res/drawable/rounded_ripple.xml b/packages/SystemUI/res/drawable/rounded_ripple.xml
index 5588eb2..d9ed823 100644
--- a/packages/SystemUI/res/drawable/rounded_ripple.xml
+++ b/packages/SystemUI/res/drawable/rounded_ripple.xml
@@ -23,7 +23,7 @@
     </item>
     <item android:id="@android:id/background">
         <shape android:shape="rectangle">
-            <solid android:color="#FFFFFFFF"/>
+            <solid android:color="?android:attr/colorBackgroundFloating"/>
             <corners android:radius="8dp"/>
         </shape>
     </item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index d63ad08..00cd5a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -265,11 +265,11 @@
             mPendingLockCheck.cancel(false);
             mPendingLockCheck = null;
         }
+        reset();
     }
 
     @Override
     public void onResume(int reason) {
-        reset();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d6e59c7..426f714 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1184,6 +1184,10 @@
         Trace.endSection();
     }
 
+    public boolean isHiding() {
+        return mHiding;
+    }
+
     /**
      * Handles SET_OCCLUDED message sent by setOccluded()
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 3d7067d..1fb1ddd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -99,6 +99,11 @@
     private int mBurnInPreventionOffsetY;
 
     /**
+     * Clock vertical padding when pulsing.
+     */
+    private int mPulsingPadding;
+
+    /**
      * Doze/AOD transition amount.
      */
     private float mDarkAmount;
@@ -109,9 +114,9 @@
     private boolean mCurrentlySecure;
 
     /**
-     * If notification panel view currently has a touch.
+     * Dozing and receiving a notification (AOD notification.)
      */
-    private boolean mTracking;
+    private boolean mPulsing;
 
     /**
      * Distance in pixels between the top of the screen and the first view of the bouncer.
@@ -130,11 +135,13 @@
                 R.dimen.burn_in_prevention_offset_x);
         mBurnInPreventionOffsetY = res.getDimensionPixelSize(
                 R.dimen.burn_in_prevention_offset_y);
+        mPulsingPadding = res.getDimensionPixelSize(
+                R.dimen.widget_pulsing_bottom_padding);
     }
 
     public void setup(int minTopMargin, int maxShadeBottom, int notificationStackHeight,
             float expandedHeight, float maxPanelHeight, int parentHeight, int keyguardStatusHeight,
-            float dark, boolean secure, boolean tracking, int bouncerTop) {
+            float dark, boolean secure, boolean pulsing, int bouncerTop) {
         mMinTopMargin = minTopMargin + mContainerTopPadding;
         mMaxShadeBottom = maxShadeBottom;
         mNotificationStackHeight = notificationStackHeight;
@@ -144,7 +151,7 @@
         mKeyguardStatusHeight = keyguardStatusHeight;
         mDarkAmount = dark;
         mCurrentlySecure = secure;
-        mTracking = tracking;
+        mPulsing = pulsing;
         mBouncerTop = bouncerTop;
     }
 
@@ -152,7 +159,7 @@
         final int y = getClockY();
         result.clockY = y;
         result.clockAlpha = getClockAlpha(y);
-        result.stackScrollerPadding = y + mKeyguardStatusHeight;
+        result.stackScrollerPadding = y + (mPulsing ? 0 : mKeyguardStatusHeight);
         result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
     }
 
@@ -194,9 +201,13 @@
 
     private int getClockY() {
         // Dark: Align the bottom edge of the clock at about half of the screen:
-        final float clockYDark = getMaxClockY() + burnInPreventionOffsetY();
-        final float clockYRegular = getExpandedClockPosition();
-        final boolean hasEnoughSpace = mMinTopMargin + mKeyguardStatusHeight < mBouncerTop;
+        float clockYDark = getMaxClockY() + burnInPreventionOffsetY();
+        if (mPulsing) {
+            clockYDark -= mPulsingPadding;
+        }
+
+        float clockYRegular = getExpandedClockPosition();
+        boolean hasEnoughSpace = mMinTopMargin + mKeyguardStatusHeight < mBouncerTop;
         float clockYTarget = mCurrentlySecure && hasEnoughSpace ?
                 mMinTopMargin : -mKeyguardStatusHeight;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index b7af84a..351633b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -238,6 +238,7 @@
     private boolean mIsFullWidth;
     private float mDarkAmount;
     private float mDarkAmountTarget;
+    private boolean mPulsing;
     private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
     private boolean mNoVisibleNotifications = true;
     private ValueAnimator mDarkAnimator;
@@ -477,7 +478,7 @@
                     mKeyguardStatusView.getHeight(),
                     mDarkAmount,
                     mStatusBar.isKeyguardCurrentlySecure(),
-                    mTracking,
+                    mPulsing,
                     mBouncerTop);
             mClockPositionAlgorithm.run(mClockPositionResult);
             if (animate || mClockAnimator != null) {
@@ -2689,14 +2690,8 @@
         positionClockAndNotifications();
     }
 
-    public void setNoVisibleNotifications(boolean noNotifications) {
-        mNoVisibleNotifications = noNotifications;
-        if (mQs != null) {
-            mQs.setHasNotifications(!noNotifications);
-        }
-    }
-
     public void setPulsing(boolean pulsing) {
+        mPulsing = pulsing;
         mKeyguardStatusView.setPulsing(pulsing);
         positionClockAndNotifications();
         mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index ab13678..4b2bc45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3942,7 +3942,8 @@
     }
 
     private void showBouncerIfKeyguard() {
-        if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
+        if ((mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)
+                && !mKeyguardViewMediator.isHiding()) {
             showBouncer(true /* animated */);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 5a4478f..639e49b 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -58,7 +58,7 @@
 
     private static final String TUNER_VERSION = "sysui_tuner_version";
 
-    private static final int CURRENT_TUNER_VERSION = 2;
+    private static final int CURRENT_TUNER_VERSION = 3;
 
     private final Observer mObserver = new Observer();
     // Map of Uris we listen on to their settings keys.
@@ -119,6 +119,10 @@
         if (oldVersion < 2) {
             setTunerEnabled(mContext, false);
         }
+        if (oldVersion < 3) {
+            // Delay this so that we can wait for everything to be registered first.
+            new Handler(Dependency.get(Dependency.BG_LOOPER)).postDelayed(() -> clearAll(), 5000);
+        }
         setValue(TUNER_VERSION, newVersion);
     }
 
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 2e4c3b8..89bf608 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5613,6 +5613,11 @@
     // OS: P
     SETTINGS_ZEN_ONBOARDING = 1380;
 
+    // OPEN: Settings > Display > Auto brightness
+    // CATEGORY: SETTINGS
+    // OS: P
+    SETTINGS_AUTO_BRIGHTNESS = 1381;
+
     // ---- End P Constants, all P constants go above this line ----
 
     // First Q constant in master goes here:
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c3f4809..cb000ba 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -767,7 +767,7 @@
     /**
      * The controller for all operations related to locktask.
      */
-    final LockTaskController mLockTaskController;
+    private final LockTaskController mLockTaskController;
 
     final UserController mUserController;
 
@@ -12315,6 +12315,10 @@
         return mActivityStartController;
     }
 
+    LockTaskController getLockTaskController() {
+        return mLockTaskController;
+    }
+
     ClientLifecycleManager getLifecycleManager() {
         return mLifecycleManager;
     }
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index e539bf8..4ce157f 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1589,14 +1589,20 @@
     void pauseKeyDispatchingLocked() {
         if (!keysPaused) {
             keysPaused = true;
-            mWindowContainerController.pauseKeyDispatching();
+
+            if (mWindowContainerController != null) {
+                mWindowContainerController.pauseKeyDispatching();
+            }
         }
     }
 
     void resumeKeyDispatchingLocked() {
         if (keysPaused) {
             keysPaused = false;
-            mWindowContainerController.resumeKeyDispatching();
+
+            if (mWindowContainerController != null) {
+                mWindowContainerController.resumeKeyDispatching();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 649d577..1e9edd9 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -3743,7 +3743,7 @@
                 }
 
                 if (endTask) {
-                    mService.mLockTaskController.clearLockedTask(task);
+                    mService.getLockTaskController().clearLockedTask(task);
                 }
             } else if (!r.isState(PAUSING)) {
                 // If the activity is PAUSING, we will complete the finish once
@@ -4639,7 +4639,7 @@
 
         // In LockTask mode, moving a locked task to the back of the stack may expose unlocked
         // ones. Therefore we need to check if this operation is allowed.
-        if (!mService.mLockTaskController.canMoveTaskToBack(tr)) {
+        if (!mService.getLockTaskController().canMoveTaskToBack(tr)) {
             return false;
         }
 
@@ -5084,7 +5084,12 @@
             onActivityRemovedFromStack(record);
         }
 
-        mTaskHistory.remove(task);
+        final boolean removed = mTaskHistory.remove(task);
+
+        if (removed) {
+            EventLog.writeEvent(EventLogTags.AM_REMOVE_TASK, task.taskId, getStackId());
+        }
+
         removeActivitiesFromLRUListLocked(task);
         updateTaskMovement(task, true);
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index e9a1358..731a44d 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1372,12 +1372,13 @@
             mService.updateLruProcessLocked(app, true, null);
             mService.updateOomAdjLocked();
 
+            final LockTaskController lockTaskController = mService.getLockTaskController();
             if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
                     || task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV
                     || (task.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED
-                            && mService.mLockTaskController.getLockTaskModeState()
-                            == LOCK_TASK_MODE_LOCKED)) {
-                mService.mLockTaskController.startLockTaskMode(task, false, 0 /* blank UID */);
+                            && lockTaskController.getLockTaskModeState()
+                                    == LOCK_TASK_MODE_LOCKED)) {
+                lockTaskController.startLockTaskMode(task, false, 0 /* blank UID */);
             }
 
             try {
@@ -2900,7 +2901,7 @@
         if (tr != null) {
             tr.removeTaskActivitiesLocked(pauseImmediately, reason);
             cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
-            mService.mLockTaskController.clearLockedTask(tr);
+            mService.getLockTaskController().clearLockedTask(tr);
             if (tr.isPersistable) {
                 mService.notifyTaskPersisterLocked(null, true);
             }
@@ -3814,7 +3815,7 @@
         pw.print(mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser));
 
         getKeyguardController().dump(pw, prefix);
-        mService.mLockTaskController.dump(pw, prefix);
+        mService.getLockTaskController().dump(pw, prefix);
     }
 
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 5337566..fb89e67 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1156,9 +1156,10 @@
             // If we are not able to proceed, disassociate the activity from the task. Leaving an
             // activity in an incomplete state can lead to issues, such as performing operations
             // without a window container.
-            if (!ActivityManager.isStartResultSuccessful(result)
-                    && mStartActivity.getTask() != null) {
-                mStartActivity.getTask().removeActivity(mStartActivity);
+            final ActivityStack stack = mStartActivity.getStack();
+            if (!ActivityManager.isStartResultSuccessful(result) && stack != null) {
+                stack.finishActivityLocked(mStartActivity, RESULT_CANCELED,
+                        null /* intentResultData */, "startActivity", true /* oomAdj */);
             }
             mService.mWindowManager.continueSurfaceLayout();
         }
@@ -1208,7 +1209,7 @@
             // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
             // still needs to be a lock task mode violation since the task gets cleared out and
             // the device would otherwise leave the locked task.
-            if (mService.mLockTaskController.isLockTaskModeViolation(reusedActivity.getTask(),
+            if (mService.getLockTaskController().isLockTaskModeViolation(reusedActivity.getTask(),
                     (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                             == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) {
                 Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode");
@@ -2020,7 +2021,7 @@
             mStartActivity.setTaskToAffiliateWith(taskToAffiliate);
         }
 
-        if (mService.mLockTaskController.isLockTaskModeViolation(mStartActivity.getTask())) {
+        if (mService.getLockTaskController().isLockTaskModeViolation(mStartActivity.getTask())) {
             Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
             return START_RETURN_LOCK_TASK_MODE_VIOLATION;
         }
@@ -2043,7 +2044,7 @@
     }
 
     private int setTaskFromSourceRecord() {
-        if (mService.mLockTaskController.isLockTaskModeViolation(mSourceRecord.getTask())) {
+        if (mService.getLockTaskController().isLockTaskModeViolation(mSourceRecord.getTask())) {
             Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
             return START_RETURN_LOCK_TASK_MODE_VIOLATION;
         }
@@ -2137,7 +2138,7 @@
     private int setTaskFromInTask() {
         // The caller is asking that the new activity be started in an explicit
         // task it has provided to us.
-        if (mService.mLockTaskController.isLockTaskModeViolation(mInTask)) {
+        if (mService.getLockTaskController().isLockTaskModeViolation(mInTask)) {
             Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
             return START_RETURN_LOCK_TASK_MODE_VIOLATION;
         }
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 9caef4a..40b9e4f 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -133,4 +133,7 @@
 # The activity's onStart has been called.
 30059 am_on_start_called (User|1|5),(Component Name|3),(Reason|3)
 # The activity's onDestroy has been called.
-30060 am_on_destroy_called (User|1|5),(Component Name|3),(Reason|3)
\ No newline at end of file
+30060 am_on_destroy_called (User|1|5),(Component Name|3),(Reason|3)
+
+# The task is being removed from its parent stack
+30061 am_remove_task (Task ID|1|5), (Stack ID|1|5)
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index 2b988d3..a20452b 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -523,7 +523,7 @@
         }
         for (int i = mTasks.size() - 1; i >= 0; --i) {
             final TaskRecord tr = mTasks.get(i);
-            if (tr.userId == userId && !mService.mLockTaskController.isTaskWhitelisted(tr)) {
+            if (tr.userId == userId && !mService.getLockTaskController().isTaskWhitelisted(tr)) {
                 remove(tr);
             }
         }
@@ -1156,7 +1156,7 @@
         }
 
         // If we're in lock task mode, ignore the root task
-        if (task == mService.mLockTaskController.getRootTask()) {
+        if (task == mService.getLockTaskController().getRootTask()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/am/SafeActivityOptions.java b/services/core/java/com/android/server/am/SafeActivityOptions.java
index ac6f01f..2de75273 100644
--- a/services/core/java/com/android/server/am/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/am/SafeActivityOptions.java
@@ -210,7 +210,7 @@
         // Check if someone tries to launch an unwhitelisted activity into LockTask mode.
         final boolean lockTaskMode = options.getLockTaskMode();
         if (aInfo != null && lockTaskMode
-                && !supervisor.mService.mLockTaskController.isPackageWhitelisted(
+                && !supervisor.mService.getLockTaskController().isPackageWhitelisted(
                         UserHandle.getUserId(callingUid), aInfo.packageName)) {
             final String msg = "Permission Denial: starting " + getIntentString(intent)
                     + " from " + callerApp + " (pid=" + callingPid
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 034cb2e..737105d 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -451,7 +451,7 @@
     }
 
     void removeWindowContainer() {
-        mService.mLockTaskController.clearLockedTask(this);
+        mService.getLockTaskController().clearLockedTask(this);
         mWindowContainerController.removeContainer();
         if (!getWindowConfiguration().persistTaskBounds()) {
             // Reset current bounds for task whose bounds shouldn't be persisted so it uses
@@ -1446,9 +1446,10 @@
         }
 
         final String pkg = (realActivity != null) ? realActivity.getPackageName() : null;
+        final LockTaskController lockTaskController = mService.getLockTaskController();
         switch (r.lockTaskLaunchMode) {
             case LOCK_TASK_LAUNCH_MODE_DEFAULT:
-                mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg)
+                mLockTaskAuth = lockTaskController.isPackageWhitelisted(userId, pkg)
                         ? LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;
                 break;
 
@@ -1461,7 +1462,7 @@
                 break;
 
             case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED:
-                mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg)
+                mLockTaskAuth = lockTaskController.isPackageWhitelisted(userId, pkg)
                         ? LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
                 break;
         }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b6e414a2..fecb934 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2215,7 +2215,7 @@
 
         protected void clearAllLockedTasks(String reason) {
             synchronized (mService) {
-                mService.mLockTaskController.clearLockedTasks(reason);
+                mService.getLockTaskController().clearLockedTasks(reason);
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a1ca702..f8c9118 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1523,7 +1523,6 @@
                                     Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER,
                                             params.traceMethod, params.traceCookie);
                                 }
-                                return;
                             }
                             mPendingInstalls.clear();
                         } else {
@@ -14036,7 +14035,7 @@
                 "setPackagesSuspended for user " + userId);
         if (callingUid != Process.ROOT_UID &&
                 !UserHandle.isSameApp(getPackageUid(callingPackage, 0, userId), callingUid)) {
-            throw new IllegalArgumentException("callingPackage " + callingPackage + " does not"
+            throw new IllegalArgumentException("CallingPackage " + callingPackage + " does not"
                     + " belong to calling app id " + UserHandle.getAppId(callingUid));
         }
 
@@ -14060,20 +14059,18 @@
                     final PackageSetting pkgSetting = mSettings.mPackages.get(packageName);
                     if (pkgSetting == null
                             || filterAppAccessLPr(pkgSetting, callingUid, userId)) {
-                        Slog.w(TAG, "Could not find package setting for package \"" + packageName
-                                + "\". Skipping suspending/un-suspending.");
+                        Slog.w(TAG, "Could not find package setting for package: " + packageName
+                                + ". Skipping suspending/un-suspending.");
                         unactionedPackages.add(packageName);
                         continue;
                     }
-                    if (pkgSetting.getSuspended(userId) != suspended) {
-                        if (!canSuspendPackageForUserLocked(packageName, userId)) {
-                            unactionedPackages.add(packageName);
-                            continue;
-                        }
-                        pkgSetting.setSuspended(suspended, callingPackage, dialogMessage, appExtras,
-                                launcherExtras, userId);
-                        changedPackagesList.add(packageName);
+                    if (!canSuspendPackageForUserLocked(packageName, userId)) {
+                        unactionedPackages.add(packageName);
+                        continue;
                     }
+                    pkgSetting.setSuspended(suspended, callingPackage, dialogMessage, appExtras,
+                            launcherExtras, userId);
+                    changedPackagesList.add(packageName);
                 }
             }
         } finally {
@@ -14088,7 +14085,6 @@
                 scheduleWritePackageRestrictionsLocked(userId);
             }
         }
-
         return unactionedPackages.toArray(new String[unactionedPackages.size()]);
     }
 
@@ -14096,7 +14092,8 @@
     public PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId) {
         final int callingUid = Binder.getCallingUid();
         if (getPackageUid(packageName, 0, userId) != callingUid) {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, null);
+            throw new SecurityException("Calling package " + packageName
+                    + " does not belong to calling uid " + callingUid);
         }
         synchronized (mPackages) {
             final PackageSetting ps = mSettings.mPackages.get(packageName);
@@ -14111,25 +14108,6 @@
         }
     }
 
-    @Override
-    public void setSuspendedPackageAppExtras(String packageName, PersistableBundle appExtras,
-            int userId) {
-        final int callingUid = Binder.getCallingUid();
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, null);
-        synchronized (mPackages) {
-            final PackageSetting ps = mSettings.mPackages.get(packageName);
-            if (ps == null || filterAppAccessLPr(ps, callingUid, userId)) {
-                throw new IllegalArgumentException("Unknown target package: " + packageName);
-            }
-            final PackageUserState packageUserState = ps.readUserState(userId);
-            if (packageUserState.suspended) {
-                packageUserState.suspendedAppExtras = appExtras;
-                sendMyPackageSuspendedOrUnsuspended(new String[] {packageName}, true, appExtras,
-                        userId);
-            }
-        }
-    }
-
     private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended,
             PersistableBundle appExtras, int userId) {
         final String action;
@@ -14184,18 +14162,26 @@
         }
     }
 
-    void onSuspendingPackageRemoved(String packageName, int userId) {
-        final int[] userIds = (userId == UserHandle.USER_ALL) ? sUserManager.getUserIds()
-                : new int[] {userId};
-        synchronized (mPackages) {
-            for (PackageSetting ps : mSettings.mPackages.values()) {
-                for (int user : userIds) {
-                    final PackageUserState pus = ps.readUserState(user);
+    void onSuspendingPackageRemoved(String packageName, int removedForUser) {
+        final int[] userIds = (removedForUser == UserHandle.USER_ALL) ? sUserManager.getUserIds()
+                : new int[] {removedForUser};
+        for (int userId : userIds) {
+            List<String> affectedPackages = new ArrayList<>();
+            synchronized (mPackages) {
+                for (PackageSetting ps : mSettings.mPackages.values()) {
+                    final PackageUserState pus = ps.readUserState(userId);
                     if (pus.suspended && packageName.equals(pus.suspendingPackage)) {
-                        ps.setSuspended(false, null, null, null, null, user);
+                        ps.setSuspended(false, null, null, null, null, userId);
+                        affectedPackages.add(ps.name);
                     }
                 }
             }
+            if (!affectedPackages.isEmpty()) {
+                final String[] packageArray = affectedPackages.toArray(
+                        new String[affectedPackages.size()]);
+                sendMyPackageSuspendedOrUnsuspended(packageArray, false, null, userId);
+                sendPackagesSuspendedForUser(packageArray, userId, false, null);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index fd4c5e9..138594c 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -20,8 +20,6 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
diff --git a/services/core/java/com/android/server/slice/DirtyTracker.java b/services/core/java/com/android/server/slice/DirtyTracker.java
new file mode 100644
index 0000000..4288edc
--- /dev/null
+++ b/services/core/java/com/android/server/slice/DirtyTracker.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.server.slice;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
+/**
+ * A parent object that cares when a Persistable changes and will schedule a serialization
+ * in response to the onPersistableDirty callback.
+ */
+public interface DirtyTracker {
+    void onPersistableDirty(Persistable obj);
+
+    /**
+     * An object that can be written to XML.
+     */
+    interface Persistable {
+        String getFileName();
+        void writeTo(XmlSerializer out) throws IOException;
+    }
+}
diff --git a/services/core/java/com/android/server/slice/SliceClientPermissions.java b/services/core/java/com/android/server/slice/SliceClientPermissions.java
new file mode 100644
index 0000000..e461e0d
--- /dev/null
+++ b/services/core/java/com/android/server/slice/SliceClientPermissions.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.server.slice;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.server.slice.DirtyTracker.Persistable;
+import com.android.server.slice.SlicePermissionManager.PkgUser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class SliceClientPermissions implements DirtyTracker, Persistable {
+
+    private static final String TAG = "SliceClientPermissions";
+
+    static final String TAG_CLIENT = "client";
+    private static final String TAG_AUTHORITY = "authority";
+    private static final String TAG_PATH = "path";
+    private static final String NAMESPACE = null;
+
+    private static final String ATTR_PKG = "pkg";
+    private static final String ATTR_AUTHORITY = "authority";
+    private static final String ATTR_FULL_ACCESS = "fullAccess";
+
+    private final PkgUser mPkg;
+    // Keyed off (authority, userId) rather than the standard (pkg, userId)
+    private final ArrayMap<PkgUser, SliceAuthority> mAuths = new ArrayMap<>();
+    private final DirtyTracker mTracker;
+    private boolean mHasFullAccess;
+
+    public SliceClientPermissions(@NonNull PkgUser pkg, @NonNull DirtyTracker tracker) {
+        mPkg = pkg;
+        mTracker = tracker;
+    }
+
+    public PkgUser getPkg() {
+        return mPkg;
+    }
+
+    public synchronized Collection<SliceAuthority> getAuthorities() {
+        return new ArrayList<>(mAuths.values());
+    }
+
+    public synchronized SliceAuthority getOrCreateAuthority(PkgUser authority, PkgUser provider) {
+        SliceAuthority ret = mAuths.get(authority);
+        if (ret == null) {
+            ret = new SliceAuthority(authority.getPkg(), provider, this);
+            mAuths.put(authority, ret);
+            onPersistableDirty(ret);
+        }
+        return ret;
+    }
+
+    public synchronized SliceAuthority getAuthority(PkgUser authority) {
+        return mAuths.get(authority);
+    }
+
+    public boolean hasFullAccess() {
+        return mHasFullAccess;
+    }
+
+    public void setHasFullAccess(boolean hasFullAccess) {
+        if (mHasFullAccess == hasFullAccess) return;
+        mHasFullAccess = hasFullAccess;
+        mTracker.onPersistableDirty(this);
+    }
+
+    public void removeAuthority(String authority, int userId) {
+        if (mAuths.remove(new PkgUser(authority, userId)) != null) {
+            mTracker.onPersistableDirty(this);
+        }
+    }
+
+    public synchronized boolean hasPermission(Uri uri, int userId) {
+        if (!Objects.equals(ContentResolver.SCHEME_CONTENT, uri.getScheme())) return false;
+        SliceAuthority authority = getAuthority(new PkgUser(uri.getAuthority(), userId));
+        return authority != null && authority.hasPermission(uri.getPathSegments());
+    }
+
+    public void grantUri(Uri uri, PkgUser providerPkg) {
+        SliceAuthority authority = getOrCreateAuthority(
+                new PkgUser(uri.getAuthority(), providerPkg.getUserId()),
+                providerPkg);
+        authority.addPath(uri.getPathSegments());
+    }
+
+    public void revokeUri(Uri uri, PkgUser providerPkg) {
+        SliceAuthority authority = getOrCreateAuthority(
+                new PkgUser(uri.getAuthority(), providerPkg.getUserId()),
+                providerPkg);
+        authority.removePath(uri.getPathSegments());
+    }
+
+    public void clear() {
+        if (!mHasFullAccess && mAuths.isEmpty()) return;
+        mHasFullAccess = false;
+        mAuths.clear();
+        onPersistableDirty(this);
+    }
+
+    @Override
+    public void onPersistableDirty(Persistable obj) {
+        mTracker.onPersistableDirty(this);
+    }
+
+    @Override
+    public String getFileName() {
+        return getFileName(mPkg);
+    }
+
+    public synchronized void writeTo(XmlSerializer out) throws IOException {
+        out.startTag(NAMESPACE, TAG_CLIENT);
+        out.attribute(NAMESPACE, ATTR_PKG, mPkg.toString());
+        out.attribute(NAMESPACE, ATTR_FULL_ACCESS, mHasFullAccess ? "1" : "0");
+
+        final int N = mAuths.size();
+        for (int i = 0; i < N; i++) {
+            out.startTag(NAMESPACE, TAG_AUTHORITY);
+            out.attribute(NAMESPACE, ATTR_AUTHORITY, mAuths.valueAt(i).mAuthority);
+            out.attribute(NAMESPACE, ATTR_PKG, mAuths.valueAt(i).mPkg.toString());
+
+            mAuths.valueAt(i).writeTo(out);
+
+            out.endTag(NAMESPACE, TAG_AUTHORITY);
+        }
+
+        out.endTag(NAMESPACE, TAG_CLIENT);
+    }
+
+    public static SliceClientPermissions createFrom(XmlPullParser parser, DirtyTracker tracker)
+            throws XmlPullParserException, IOException {
+        // Get to the beginning of the provider.
+        while (parser.getEventType() != XmlPullParser.START_TAG
+                || !TAG_CLIENT.equals(parser.getName())) {
+            parser.next();
+        }
+        int depth = parser.getDepth();
+        PkgUser pkgUser = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG));
+        SliceClientPermissions provider = new SliceClientPermissions(pkgUser, tracker);
+        String fullAccess = parser.getAttributeValue(NAMESPACE, ATTR_FULL_ACCESS);
+        if (fullAccess == null) {
+            fullAccess = "0";
+        }
+        provider.mHasFullAccess = Integer.parseInt(fullAccess) != 0;
+        parser.next();
+
+        while (parser.getDepth() > depth) {
+            if (parser.getEventType() == XmlPullParser.START_TAG
+                    && TAG_AUTHORITY.equals(parser.getName())) {
+                try {
+                    PkgUser pkg = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG));
+                    SliceAuthority authority = new SliceAuthority(
+                            parser.getAttributeValue(NAMESPACE, ATTR_AUTHORITY), pkg, provider);
+                    authority.readFrom(parser);
+                    provider.mAuths.put(new PkgUser(authority.getAuthority(), pkg.getUserId()),
+                            authority);
+                } catch (IllegalArgumentException e) {
+                    Slog.e(TAG, "Couldn't read PkgUser", e);
+                }
+            }
+
+            parser.next();
+        }
+        return provider;
+    }
+
+    public static String getFileName(PkgUser pkg) {
+        return String.format("client_%s", pkg.toString());
+    }
+
+    public static class SliceAuthority implements Persistable {
+        public static final String DELIMITER = "/";
+        private final String mAuthority;
+        private final DirtyTracker mTracker;
+        private final PkgUser mPkg;
+        private final ArraySet<String[]> mPaths = new ArraySet<>();
+
+        public SliceAuthority(String authority, PkgUser pkg, DirtyTracker tracker) {
+            mAuthority = authority;
+            mPkg = pkg;
+            mTracker = tracker;
+        }
+
+        public String getAuthority() {
+            return mAuthority;
+        }
+
+        public PkgUser getPkg() {
+            return mPkg;
+        }
+
+        void addPath(List<String> path) {
+            String[] pathSegs = path.toArray(new String[path.size()]);
+            for (int i = mPaths.size() - 1; i >= 0; i--) {
+                String[] existing = mPaths.valueAt(i);
+                if (isPathPrefixMatch(existing, pathSegs)) {
+                    // Nothing to add here.
+                    return;
+                }
+                if (isPathPrefixMatch(pathSegs, existing)) {
+                    mPaths.removeAt(i);
+                }
+            }
+            mPaths.add(pathSegs);
+            mTracker.onPersistableDirty(this);
+        }
+
+        void removePath(List<String> path) {
+            boolean changed = false;
+            String[] pathSegs = path.toArray(new String[path.size()]);
+            for (int i = mPaths.size() - 1; i >= 0; i--) {
+                String[] existing = mPaths.valueAt(i);
+                if (isPathPrefixMatch(pathSegs, existing)) {
+                    changed = true;
+                    mPaths.removeAt(i);
+                }
+            }
+            if (changed) {
+                mTracker.onPersistableDirty(this);
+            }
+        }
+
+        public synchronized Collection<String[]> getPaths() {
+            return new ArraySet<>(mPaths);
+        }
+
+        public boolean hasPermission(List<String> path) {
+            for (String[] p : mPaths) {
+                if (isPathPrefixMatch(p, path.toArray(new String[path.size()]))) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private boolean isPathPrefixMatch(String[] prefix, String[] path) {
+            final int prefixSize = prefix.length;
+            if (path.length < prefixSize) return false;
+
+            for (int i = 0; i < prefixSize; i++) {
+                if (!Objects.equals(path[i], prefix[i])) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        @Override
+        public String getFileName() {
+            return null;
+        }
+
+        public synchronized void writeTo(XmlSerializer out) throws IOException {
+            final int N = mPaths.size();
+            for (int i = 0; i < N; i++) {
+                out.startTag(NAMESPACE, TAG_PATH);
+                out.text(encodeSegments(mPaths.valueAt(i)));
+                out.endTag(NAMESPACE, TAG_PATH);
+            }
+        }
+
+        public synchronized void readFrom(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            parser.next();
+            int depth = parser.getDepth();
+            while (parser.getDepth() >= depth) {
+                if (parser.getEventType() == XmlPullParser.START_TAG
+                        && TAG_PATH.equals(parser.getName())) {
+                    mPaths.add(decodeSegments(parser.nextText()));
+                }
+                parser.next();
+            }
+        }
+
+        private String encodeSegments(String[] s) {
+            String[] out = new String[s.length];
+            for (int i = 0; i < s.length; i++) {
+                out[i] = Uri.encode(s[i]);
+            }
+            return TextUtils.join(DELIMITER, out);
+        }
+
+        private String[] decodeSegments(String s) {
+            String[] sets = s.split(DELIMITER, -1);
+            for (int i = 0; i < sets.length; i++) {
+                sets[i] = Uri.decode(sets[i]);
+            }
+            return sets;
+        }
+
+        /**
+         * Only for testing, no deep equality of these are done normally.
+         */
+        @Override
+        public boolean equals(Object obj) {
+            if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
+            SliceAuthority other = (SliceAuthority) obj;
+            if (mPaths.size() != other.mPaths.size()) return false;
+            ArrayList<String[]> p1 = new ArrayList<>(mPaths);
+            ArrayList<String[]> p2 = new ArrayList<>(other.mPaths);
+            p1.sort(Comparator.comparing(o -> TextUtils.join(",", o)));
+            p2.sort(Comparator.comparing(o -> TextUtils.join(",", o)));
+            for (int i = 0; i < p1.size(); i++) {
+                String[] a1 = p1.get(i);
+                String[] a2 = p2.get(i);
+                if (a1.length != a2.length) return false;
+                for (int j = 0; j < a1.length; j++) {
+                    if (!Objects.equals(a1[j], a2[j])) return false;
+                }
+            }
+            return Objects.equals(mAuthority, other.mAuthority)
+                    && Objects.equals(mPkg, other.mPkg);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("(%s, %s: %s)", mAuthority, mPkg.toString(), pathToString(mPaths));
+        }
+
+        private String pathToString(ArraySet<String[]> paths) {
+            return TextUtils.join(", ", paths.stream().map(s -> TextUtils.join("/", s))
+                    .collect(Collectors.toList()));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java
index fd0b6f1..b7b9612 100644
--- a/services/core/java/com/android/server/slice/SliceManagerService.java
+++ b/services/core/java/com/android/server/slice/SliceManagerService.java
@@ -31,14 +31,15 @@
 import android.app.ContentProviderHolder;
 import android.app.IActivityManager;
 import android.app.slice.ISliceManager;
-import android.app.slice.SliceManager;
 import android.app.slice.SliceSpec;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentProvider;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
@@ -51,7 +52,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml.Encoding;
@@ -72,7 +72,6 @@
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -91,13 +90,9 @@
 
     @GuardedBy("mLock")
     private final ArrayMap<Uri, PinnedSliceState> mPinnedSlicesByUri = new ArrayMap<>();
-    @GuardedBy("mLock")
-    private final ArraySet<SliceGrant> mUserGrants = new ArraySet<>();
     private final Handler mHandler;
-    @GuardedBy("mSliceAccessFile")
-    private final AtomicFile mSliceAccessFile;
-    @GuardedBy("mAccessList")
-    private final SliceFullAccessList mAccessList;
+
+    private final SlicePermissionManager mPermissions;
     private final UsageStatsManagerInternal mAppUsageStats;
 
     public SliceManagerService(Context context) {
@@ -113,24 +108,9 @@
         mAssistUtils = new AssistUtils(context);
         mHandler = new Handler(looper);
 
-        final File systemDir = new File(Environment.getDataDirectory(), "system");
-        mSliceAccessFile = new AtomicFile(new File(systemDir, "slice_access.xml"));
-        mAccessList = new SliceFullAccessList(mContext);
         mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
 
-        synchronized (mSliceAccessFile) {
-            if (!mSliceAccessFile.exists()) return;
-            try {
-                InputStream input = mSliceAccessFile.openRead();
-                XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
-                parser.setInput(input, Encoding.UTF_8.name());
-                synchronized (mAccessList) {
-                    mAccessList.readXml(parser);
-                }
-            } catch (IOException | XmlPullParserException e) {
-                Slog.d(TAG, "Can't read slice access file", e);
-            }
-        }
+        mPermissions = new SlicePermissionManager(mContext, looper);
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
@@ -211,26 +191,58 @@
     }
 
     @Override
+    public void grantSlicePermission(String pkg, String toPkg, Uri uri) throws RemoteException {
+        verifyCaller(pkg);
+        int user = Binder.getCallingUserHandle().getIdentifier();
+        enforceOwner(pkg, uri, user);
+        mPermissions.grantSliceAccess(toPkg, user, pkg, user, uri);
+    }
+
+    @Override
+    public void revokeSlicePermission(String pkg, String toPkg, Uri uri) throws RemoteException {
+        verifyCaller(pkg);
+        int user = Binder.getCallingUserHandle().getIdentifier();
+        enforceOwner(pkg, uri, user);
+        mPermissions.revokeSliceAccess(toPkg, user, pkg, user, uri);
+    }
+
+    @Override
     public int checkSlicePermission(Uri uri, String pkg, int pid, int uid,
-            String[] autoGrantPermissions) throws RemoteException {
+            String[] autoGrantPermissions) {
+        int userId = UserHandle.getUserId(uid);
+        if (pkg == null) {
+            for (String p : mContext.getPackageManager().getPackagesForUid(uid)) {
+                if (checkSlicePermission(uri, p, pid, uid, autoGrantPermissions)
+                        == PERMISSION_GRANTED) {
+                    return PERMISSION_GRANTED;
+                }
+            }
+            return PERMISSION_DENIED;
+        }
+        if (hasFullSliceAccess(pkg, userId)) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+        if (mPermissions.hasPermission(pkg, userId, uri)) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+        if (autoGrantPermissions != null) {
+            // Need to own the Uri to call in with permissions to grant.
+            enforceOwner(pkg, uri, userId);
+            for (String perm : autoGrantPermissions) {
+                if (mContext.checkPermission(perm, pid, uid) == PERMISSION_GRANTED) {
+                    int providerUser = ContentProvider.getUserIdFromUri(uri, userId);
+                    String providerPkg = getProviderPkg(uri, providerUser);
+                    mPermissions.grantSliceAccess(pkg, userId, providerPkg, providerUser, uri);
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+            }
+        }
+        // Fallback to allowing uri permissions through.
         if (mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
                 == PERMISSION_GRANTED) {
-            return SliceManager.PERMISSION_GRANTED;
+            return PackageManager.PERMISSION_GRANTED;
         }
-        if (hasFullSliceAccess(pkg, UserHandle.getUserId(uid))) {
-            return SliceManager.PERMISSION_GRANTED;
-        }
-        for (String perm : autoGrantPermissions) {
-            if (mContext.checkPermission(perm, pid, uid) == PERMISSION_GRANTED) {
-                return SliceManager.PERMISSION_USER_GRANTED;
-            }
-        }
-        synchronized (mLock) {
-            if (mUserGrants.contains(new SliceGrant(uri, pkg, UserHandle.getUserId(uid)))) {
-                return SliceManager.PERMISSION_USER_GRANTED;
-            }
-        }
-        return SliceManager.PERMISSION_DENIED;
+        return PackageManager.PERMISSION_DENIED;
     }
 
     @Override
@@ -238,16 +250,17 @@
         verifyCaller(callingPkg);
         getContext().enforceCallingOrSelfPermission(permission.MANAGE_SLICE_PERMISSIONS,
                 "Slice granting requires MANAGE_SLICE_PERMISSIONS");
+        int userId = Binder.getCallingUserHandle().getIdentifier();
         if (allSlices) {
-            synchronized (mAccessList) {
-                mAccessList.grantFullAccess(pkg, Binder.getCallingUserHandle().getIdentifier());
-            }
-            mHandler.post(mSaveAccessList);
+            mPermissions.grantFullAccess(pkg, userId);
         } else {
-            synchronized (mLock) {
-                mUserGrants.add(new SliceGrant(uri, pkg,
-                        Binder.getCallingUserHandle().getIdentifier()));
-            }
+            // When granting, grant to all slices in the provider.
+            Uri grantUri = uri.buildUpon()
+                    .path("")
+                    .build();
+            int providerUser = ContentProvider.getUserIdFromUri(grantUri, userId);
+            String providerPkg = getProviderPkg(grantUri, providerUser);
+            mPermissions.grantSliceAccess(pkg, userId, providerPkg, providerUser, grantUri);
         }
         long ident = Binder.clearCallingIdentity();
         try {
@@ -268,19 +281,17 @@
             Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user);
             return null;
         }
-        synchronized(mSliceAccessFile) {
-            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            try {
-                XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
-                out.setOutput(baos, Encoding.UTF_8.name());
-                synchronized (mAccessList) {
-                    mAccessList.writeXml(out, user);
-                }
-                out.flush();
-                return baos.toByteArray();
-            } catch (IOException | XmlPullParserException e) {
-                Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
-            }
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try {
+            XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
+            out.setOutput(baos, Encoding.UTF_8.name());
+
+            mPermissions.writeBackup(out);
+
+            out.flush();
+            return baos.toByteArray();
+        } catch (IOException | XmlPullParserException e) {
+            Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
         }
         return null;
     }
@@ -299,27 +310,21 @@
             Slog.w(TAG, "applyRestore: cannot restore policy for user " + user);
             return;
         }
-        synchronized(mSliceAccessFile) {
-            final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
-            try {
-                XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
-                parser.setInput(bais, Encoding.UTF_8.name());
-                synchronized (mAccessList) {
-                    mAccessList.readXml(parser);
-                }
-                mHandler.post(mSaveAccessList);
-            } catch (NumberFormatException | XmlPullParserException | IOException e) {
-                Slog.w(TAG, "applyRestore: error reading payload", e);
-            }
+        final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
+        try {
+            XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+            parser.setInput(bais, Encoding.UTF_8.name());
+            mPermissions.readRestore(parser);
+        } catch (NumberFormatException | XmlPullParserException | IOException e) {
+            Slog.w(TAG, "applyRestore: error reading payload", e);
         }
     }
 
     ///  ----- internal code -----
-    private void removeFullAccess(String pkg, int userId) {
-        synchronized (mAccessList) {
-            mAccessList.removeGrant(pkg, userId);
+    private void enforceOwner(String pkg, Uri uri, int user) {
+        if (!Objects.equals(getProviderPkg(uri, user), pkg) || pkg == null) {
+            throw new SecurityException("Caller must own " + uri);
         }
-        mHandler.post(mSaveAccessList);
     }
 
     protected void removePinnedSlice(Uri uri) {
@@ -368,19 +373,7 @@
     }
 
     protected int checkAccess(String pkg, Uri uri, int uid, int pid) {
-        int user = UserHandle.getUserId(uid);
-        // Check for default launcher/assistant.
-        if (!hasFullSliceAccess(pkg, user)) {
-            // Also allow things with uri access.
-            if (getContext().checkUriPermission(uri, pid, uid,
-                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PERMISSION_GRANTED) {
-                // Last fallback (if the calling app owns the authority, then it can have access).
-                if (!Objects.equals(getProviderPkg(uri, user), pkg)) {
-                    return PERMISSION_DENIED;
-                }
-            }
-        }
-        return PERMISSION_GRANTED;
+        return checkSlicePermission(uri, pkg, uid, pid, null);
     }
 
     private String getProviderPkg(Uri uri, int user) {
@@ -425,15 +418,11 @@
     private void enforceAccess(String pkg, Uri uri) throws RemoteException {
         if (checkAccess(pkg, uri, Binder.getCallingUid(), Binder.getCallingPid())
                 != PERMISSION_GRANTED) {
-            throw new SecurityException("Access to slice " + uri + " is required");
-        }
-        enforceCrossUser(pkg, uri);
-    }
-
-    private void enforceFullAccess(String pkg, String name, Uri uri) {
-        int user = Binder.getCallingUserHandle().getIdentifier();
-        if (!hasFullSliceAccess(pkg, user)) {
-            throw new SecurityException(String.format("Call %s requires full slice access", name));
+            int userId = ContentProvider.getUserIdFromUri(uri,
+                    Binder.getCallingUserHandle().getIdentifier());
+            if (!Objects.equals(pkg, getProviderPkg(uri, userId))) {
+                throw new SecurityException("Access to slice " + uri + " is required");
+            }
         }
         enforceCrossUser(pkg, uri);
     }
@@ -513,9 +502,7 @@
     }
 
     private boolean isGrantedFullAccess(String pkg, int userId) {
-        synchronized (mAccessList) {
-            return mAccessList.hasFullAccess(pkg, userId);
-        }
+        return mPermissions.hasFullAccess(pkg, userId);
     }
 
     private static ServiceThread createHandler() {
@@ -525,34 +512,6 @@
         return handlerThread;
     }
 
-    private final Runnable mSaveAccessList = new Runnable() {
-        @Override
-        public void run() {
-            synchronized (mSliceAccessFile) {
-                final FileOutputStream stream;
-                try {
-                    stream = mSliceAccessFile.startWrite();
-                } catch (IOException e) {
-                    Slog.w(TAG, "Failed to save access file", e);
-                    return;
-                }
-
-                try {
-                    XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
-                    out.setOutput(stream, Encoding.UTF_8.name());
-                    synchronized (mAccessList) {
-                        mAccessList.writeXml(out, UserHandle.USER_ALL);
-                    }
-                    out.flush();
-                    mSliceAccessFile.finishWrite(stream);
-                } catch (IOException | XmlPullParserException e) {
-                    Slog.w(TAG, "Failed to save access file, restoring backup", e);
-                    mSliceAccessFile.failWrite(stream);
-                }
-            }
-        }
-    };
-
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -572,11 +531,11 @@
                     final boolean replacing =
                             intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
                     if (!replacing) {
-                        removeFullAccess(pkg, userId);
+                        mPermissions.removePkg(pkg, userId);
                     }
                     break;
                 case Intent.ACTION_PACKAGE_DATA_CLEARED:
-                    removeFullAccess(pkg, userId);
+                    mPermissions.removePkg(pkg, userId);
                     break;
             }
         }
diff --git a/services/core/java/com/android/server/slice/SlicePermissionManager.java b/services/core/java/com/android/server/slice/SlicePermissionManager.java
new file mode 100644
index 0000000..d25ec89
--- /dev/null
+++ b/services/core/java/com/android/server/slice/SlicePermissionManager.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.server.slice;
+
+import android.content.ContentProvider;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml.Encoding;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.XmlUtils;
+import com.android.server.slice.SliceProviderPermissions.SliceAuthority;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Objects;
+
+public class SlicePermissionManager implements DirtyTracker {
+
+    private static final String TAG = "SlicePermissionManager";
+
+    /**
+     * The amount of time we'll cache a SliceProviderPermissions or SliceClientPermissions
+     * in case they are used again.
+     */
+    private static final long PERMISSION_CACHE_PERIOD = 5 * DateUtils.MINUTE_IN_MILLIS;
+
+    /**
+     * The amount of time we delay flushing out permission changes to disk because they usually
+     * come in short bursts.
+     */
+    private static final long WRITE_GRACE_PERIOD = 500;
+
+    private static final String SLICE_DIR = "slice";
+
+    // If/when this bumps again we'll need to write it out in the disk somewhere.
+    // Currently we don't have a central file for this in version 2 and there is no
+    // reason to add one until we actually have incompatible version bumps.
+    // This does however block us from reading backups from P-DP1 which may contain
+    // a very different XML format for perms.
+    static final int DB_VERSION = 2;
+
+    private static final String TAG_LIST = "slice-access-list";
+    private final String ATT_VERSION = "version";
+
+    private final File mSliceDir;
+    private final Context mContext;
+    private final Handler mHandler;
+    private final ArrayMap<PkgUser, SliceProviderPermissions> mCachedProviders = new ArrayMap<>();
+    private final ArrayMap<PkgUser, SliceClientPermissions> mCachedClients = new ArrayMap<>();
+    private final ArraySet<Persistable> mDirty = new ArraySet<>();
+
+    @VisibleForTesting
+    SlicePermissionManager(Context context, Looper looper, File sliceDir) {
+        mContext = context;
+        mHandler = new H(looper);
+        mSliceDir = sliceDir;
+    }
+
+    public SlicePermissionManager(Context context, Looper looper) {
+        this(context, looper, new File(Environment.getDataDirectory(), "system/" + SLICE_DIR));
+    }
+
+    public void grantFullAccess(String pkg, int userId) {
+        PkgUser pkgUser = new PkgUser(pkg, userId);
+        SliceClientPermissions client = getClient(pkgUser);
+        client.setHasFullAccess(true);
+    }
+
+    public void grantSliceAccess(String pkg, int userId, String providerPkg, int providerUser,
+            Uri uri) {
+        PkgUser pkgUser = new PkgUser(pkg, userId);
+        PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser);
+
+        SliceClientPermissions client = getClient(pkgUser);
+        client.grantUri(uri, providerPkgUser);
+
+        SliceProviderPermissions provider = getProvider(providerPkgUser);
+        provider.getOrCreateAuthority(ContentProvider.getUriWithoutUserId(uri).getAuthority())
+                .addPkg(pkgUser);
+    }
+
+    public void revokeSliceAccess(String pkg, int userId, String providerPkg, int providerUser,
+            Uri uri) {
+        PkgUser pkgUser = new PkgUser(pkg, userId);
+        PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser);
+
+        SliceClientPermissions client = getClient(pkgUser);
+        client.revokeUri(uri, providerPkgUser);
+    }
+
+    public void removePkg(String pkg, int userId) {
+        PkgUser pkgUser = new PkgUser(pkg, userId);
+        SliceProviderPermissions provider = getProvider(pkgUser);
+
+        for (SliceAuthority authority : provider.getAuthorities()) {
+            for (PkgUser p : authority.getPkgs()) {
+                getClient(p).removeAuthority(authority.getAuthority(), userId);
+            }
+        }
+        SliceClientPermissions client = getClient(pkgUser);
+        client.clear();
+        mHandler.obtainMessage(H.MSG_REMOVE, pkgUser);
+    }
+
+    public boolean hasFullAccess(String pkg, int userId) {
+        PkgUser pkgUser = new PkgUser(pkg, userId);
+        return getClient(pkgUser).hasFullAccess();
+    }
+
+    public boolean hasPermission(String pkg, int userId, Uri uri) {
+        PkgUser pkgUser = new PkgUser(pkg, userId);
+        SliceClientPermissions client = getClient(pkgUser);
+        int providerUserId = ContentProvider.getUserIdFromUri(uri, userId);
+        return client.hasFullAccess()
+                || client.hasPermission(ContentProvider.getUriWithoutUserId(uri), providerUserId);
+    }
+
+    @Override
+    public void onPersistableDirty(Persistable obj) {
+        mHandler.removeMessages(H.MSG_PERSIST);
+        mHandler.obtainMessage(H.MSG_ADD_DIRTY, obj).sendToTarget();
+        mHandler.sendEmptyMessageDelayed(H.MSG_PERSIST, WRITE_GRACE_PERIOD);
+    }
+
+    public void writeBackup(XmlSerializer out) throws IOException, XmlPullParserException {
+        synchronized (this) {
+            out.startTag(null, TAG_LIST);
+            out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION));
+
+            // Don't do anything with changes from the backup, because there shouldn't be any.
+            DirtyTracker tracker = obj -> { };
+            if (mHandler.hasMessages(H.MSG_PERSIST)) {
+                mHandler.removeMessages(H.MSG_PERSIST);
+                handlePersist();
+            }
+            for (String file : new File(mSliceDir.getAbsolutePath()).list()) {
+                if (file.isEmpty()) continue;
+                try (ParserHolder parser = getParser(file)) {
+                    Persistable p;
+                    while (parser.parser.getEventType() != XmlPullParser.START_TAG) {
+                        parser.parser.next();
+                    }
+                    if (SliceClientPermissions.TAG_CLIENT.equals(parser.parser.getName())) {
+                        p = SliceClientPermissions.createFrom(parser.parser, tracker);
+                    } else {
+                        p = SliceProviderPermissions.createFrom(parser.parser, tracker);
+                    }
+                    p.writeTo(out);
+                }
+            }
+
+            out.endTag(null, TAG_LIST);
+        }
+    }
+
+    public void readRestore(XmlPullParser parser) throws IOException, XmlPullParserException {
+        synchronized (this) {
+            while ((parser.getEventType() != XmlPullParser.START_TAG
+                    || !TAG_LIST.equals(parser.getName()))
+                    && parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+                parser.next();
+            }
+            int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0);
+            if (xmlVersion < DB_VERSION) {
+                // No conversion support right now.
+                return;
+            }
+            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+                if (parser.getEventType() == XmlPullParser.START_TAG) {
+                    if (SliceClientPermissions.TAG_CLIENT.equals(parser.getName())) {
+                        SliceClientPermissions client = SliceClientPermissions.createFrom(parser,
+                                this);
+                        synchronized (mCachedClients) {
+                            mCachedClients.put(client.getPkg(), client);
+                        }
+                        onPersistableDirty(client);
+                        mHandler.sendMessageDelayed(
+                                mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, client.getPkg()),
+                                PERMISSION_CACHE_PERIOD);
+                    } else if (SliceProviderPermissions.TAG_PROVIDER.equals(parser.getName())) {
+                        SliceProviderPermissions provider = SliceProviderPermissions.createFrom(
+                                parser, this);
+                        synchronized (mCachedProviders) {
+                            mCachedProviders.put(provider.getPkg(), provider);
+                        }
+                        onPersistableDirty(provider);
+                        mHandler.sendMessageDelayed(
+                                mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, provider.getPkg()),
+                                PERMISSION_CACHE_PERIOD);
+                    } else {
+                        parser.next();
+                    }
+                } else {
+                    parser.next();
+                }
+            }
+        }
+    }
+
+    private SliceClientPermissions getClient(PkgUser pkgUser) {
+        SliceClientPermissions client;
+        synchronized (mCachedClients) {
+            client = mCachedClients.get(pkgUser);
+        }
+        if (client == null) {
+            try (ParserHolder parser = getParser(SliceClientPermissions.getFileName(pkgUser))) {
+                client = SliceClientPermissions.createFrom(parser.parser, this);
+                synchronized (mCachedClients) {
+                    mCachedClients.put(pkgUser, client);
+                }
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, pkgUser),
+                        PERMISSION_CACHE_PERIOD);
+                return client;
+            } catch (FileNotFoundException e) {
+                // No client exists yet.
+            } catch (IOException e) {
+                Log.e(TAG, "Can't read client", e);
+            } catch (XmlPullParserException e) {
+                Log.e(TAG, "Can't read client", e);
+            }
+            // Can't read or no permissions exist, create a clean object.
+            client = new SliceClientPermissions(pkgUser, this);
+        }
+        return client;
+    }
+
+    private SliceProviderPermissions getProvider(PkgUser pkgUser) {
+        SliceProviderPermissions provider;
+        synchronized (mCachedProviders) {
+            provider = mCachedProviders.get(pkgUser);
+        }
+        if (provider == null) {
+            try (ParserHolder parser = getParser(SliceProviderPermissions.getFileName(pkgUser))) {
+                provider = SliceProviderPermissions.createFrom(parser.parser, this);
+                synchronized (mCachedProviders) {
+                    mCachedProviders.put(pkgUser, provider);
+                }
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, pkgUser),
+                        PERMISSION_CACHE_PERIOD);
+                return provider;
+            } catch (FileNotFoundException e) {
+                // No provider exists yet.
+            } catch (IOException e) {
+                Log.e(TAG, "Can't read provider", e);
+            } catch (XmlPullParserException e) {
+                Log.e(TAG, "Can't read provider", e);
+            }
+            // Can't read or no permissions exist, create a clean object.
+            provider = new SliceProviderPermissions(pkgUser, this);
+        }
+        return provider;
+    }
+
+    private ParserHolder getParser(String fileName)
+            throws FileNotFoundException, XmlPullParserException {
+        AtomicFile file = getFile(fileName);
+        ParserHolder holder = new ParserHolder();
+        holder.input = file.openRead();
+        holder.parser = XmlPullParserFactory.newInstance().newPullParser();
+        holder.parser.setInput(holder.input, Encoding.UTF_8.name());
+        return holder;
+    }
+
+    private AtomicFile getFile(String fileName) {
+        if (!mSliceDir.exists()) {
+            mSliceDir.mkdir();
+        }
+        return new AtomicFile(new File(mSliceDir, fileName));
+    }
+
+    private void handlePersist() {
+        synchronized (this) {
+            for (Persistable persistable : mDirty) {
+                AtomicFile file = getFile(persistable.getFileName());
+                final FileOutputStream stream;
+                try {
+                    stream = file.startWrite();
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failed to save access file", e);
+                    return;
+                }
+
+                try {
+                    XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
+                    out.setOutput(stream, Encoding.UTF_8.name());
+
+                    persistable.writeTo(out);
+
+                    out.flush();
+                    file.finishWrite(stream);
+                } catch (IOException | XmlPullParserException e) {
+                    Slog.w(TAG, "Failed to save access file, restoring backup", e);
+                    file.failWrite(stream);
+                }
+            }
+            mDirty.clear();
+        }
+    }
+
+    private void handleRemove(PkgUser pkgUser) {
+        getFile(SliceClientPermissions.getFileName(pkgUser)).delete();
+        getFile(SliceProviderPermissions.getFileName(pkgUser)).delete();
+        mDirty.remove(mCachedClients.remove(pkgUser));
+        mDirty.remove(mCachedProviders.remove(pkgUser));
+    }
+
+    private final class H extends Handler {
+        private static final int MSG_ADD_DIRTY = 1;
+        private static final int MSG_PERSIST = 2;
+        private static final int MSG_REMOVE = 3;
+        private static final int MSG_CLEAR_CLIENT = 4;
+        private static final int MSG_CLEAR_PROVIDER = 5;
+
+        public H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ADD_DIRTY:
+                    mDirty.add((Persistable) msg.obj);
+                    break;
+                case MSG_PERSIST:
+                    handlePersist();
+                    break;
+                case MSG_REMOVE:
+                    handleRemove((PkgUser) msg.obj);
+                    break;
+                case MSG_CLEAR_CLIENT:
+                    synchronized (mCachedClients) {
+                        mCachedClients.remove(msg.obj);
+                    }
+                    break;
+                case MSG_CLEAR_PROVIDER:
+                    synchronized (mCachedProviders) {
+                        mCachedProviders.remove(msg.obj);
+                    }
+                    break;
+            }
+        }
+    }
+
+    public static class PkgUser {
+        private static final String SEPARATOR = "@";
+        private static final String FORMAT = "%s" + SEPARATOR + "%d";
+        private final String mPkg;
+        private final int mUserId;
+
+        public PkgUser(String pkg, int userId) {
+            mPkg = pkg;
+            mUserId = userId;
+        }
+
+        public PkgUser(String pkgUserStr) throws IllegalArgumentException {
+            try {
+                String[] vals = pkgUserStr.split(SEPARATOR, 2);
+                mPkg = vals[0];
+                mUserId = Integer.parseInt(vals[1]);
+            } catch (Exception e) {
+                throw new IllegalArgumentException(e);
+            }
+        }
+
+        public String getPkg() {
+            return mPkg;
+        }
+
+        public int getUserId() {
+            return mUserId;
+        }
+
+        @Override
+        public int hashCode() {
+            return mPkg.hashCode() + mUserId;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
+            PkgUser other = (PkgUser) obj;
+            return Objects.equals(other.mPkg, mPkg) && other.mUserId == mUserId;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(FORMAT, mPkg, mUserId);
+        }
+    }
+
+    private class ParserHolder implements AutoCloseable {
+
+        private InputStream input;
+        private XmlPullParser parser;
+
+        @Override
+        public void close() throws IOException {
+            input.close();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/slice/SliceProviderPermissions.java b/services/core/java/com/android/server/slice/SliceProviderPermissions.java
new file mode 100644
index 0000000..6e602d5
--- /dev/null
+++ b/services/core/java/com/android/server/slice/SliceProviderPermissions.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.server.slice;
+
+import android.annotation.NonNull;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.server.slice.DirtyTracker.Persistable;
+import com.android.server.slice.SlicePermissionManager.PkgUser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Objects;
+
+public class SliceProviderPermissions implements DirtyTracker, Persistable {
+
+    private static final String TAG = "SliceProviderPermissions";
+
+    static final String TAG_PROVIDER = "provider";
+    private static final String TAG_AUTHORITY = "authority";
+    private static final String TAG_PKG = "pkg";
+    private static final String NAMESPACE = null;
+
+    private static final String ATTR_PKG = "pkg";
+    private static final String ATTR_AUTHORITY = "authority";
+
+    private final PkgUser mPkg;
+    private final ArrayMap<String, SliceAuthority> mAuths = new ArrayMap<>();
+    private final DirtyTracker mTracker;
+
+    public SliceProviderPermissions(@NonNull PkgUser pkg, @NonNull DirtyTracker tracker) {
+        mPkg = pkg;
+        mTracker = tracker;
+    }
+
+    public PkgUser getPkg() {
+        return mPkg;
+    }
+
+    public synchronized Collection<SliceAuthority> getAuthorities() {
+        return new ArrayList<>(mAuths.values());
+    }
+
+    public synchronized SliceAuthority getOrCreateAuthority(String authority) {
+        SliceAuthority ret = mAuths.get(authority);
+        if (ret == null) {
+            ret = new SliceAuthority(authority, this);
+            mAuths.put(authority, ret);
+            onPersistableDirty(ret);
+        }
+        return ret;
+    }
+
+    @Override
+    public void onPersistableDirty(Persistable obj) {
+        mTracker.onPersistableDirty(this);
+    }
+
+    @Override
+    public String getFileName() {
+        return getFileName(mPkg);
+    }
+
+    public synchronized void writeTo(XmlSerializer out) throws IOException {
+        out.startTag(NAMESPACE, TAG_PROVIDER);
+        out.attribute(NAMESPACE, ATTR_PKG, mPkg.toString());
+
+        final int N = mAuths.size();
+        for (int i = 0; i < N; i++) {
+            out.startTag(NAMESPACE, TAG_AUTHORITY);
+            out.attribute(NAMESPACE, ATTR_AUTHORITY, mAuths.valueAt(i).mAuthority);
+
+            mAuths.valueAt(i).writeTo(out);
+
+            out.endTag(NAMESPACE, TAG_AUTHORITY);
+        }
+
+        out.endTag(NAMESPACE, TAG_PROVIDER);
+    }
+
+    public static SliceProviderPermissions createFrom(XmlPullParser parser, DirtyTracker tracker)
+            throws XmlPullParserException, IOException {
+        // Get to the beginning of the provider.
+        while (parser.getEventType() != XmlPullParser.START_TAG
+                || !TAG_PROVIDER.equals(parser.getName())) {
+            parser.next();
+        }
+        int depth = parser.getDepth();
+        PkgUser pkgUser = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG));
+        SliceProviderPermissions provider = new SliceProviderPermissions(pkgUser, tracker);
+        parser.next();
+
+        while (parser.getDepth() > depth) {
+            if (parser.getEventType() == XmlPullParser.START_TAG
+                    && TAG_AUTHORITY.equals(parser.getName())) {
+                try {
+                    SliceAuthority authority = new SliceAuthority(
+                            parser.getAttributeValue(NAMESPACE, ATTR_AUTHORITY), provider);
+                    authority.readFrom(parser);
+                    provider.mAuths.put(authority.getAuthority(), authority);
+                } catch (IllegalArgumentException e) {
+                    Slog.e(TAG, "Couldn't read PkgUser", e);
+                }
+            }
+
+            parser.next();
+        }
+        return provider;
+    }
+
+    public static String getFileName(PkgUser pkg) {
+        return String.format("provider_%s", pkg.toString());
+    }
+
+    public static class SliceAuthority implements Persistable {
+        private final String mAuthority;
+        private final DirtyTracker mTracker;
+        private final ArraySet<PkgUser> mPkgs = new ArraySet<>();
+
+        public SliceAuthority(String authority, DirtyTracker tracker) {
+            mAuthority = authority;
+            mTracker = tracker;
+        }
+
+        public String getAuthority() {
+            return mAuthority;
+        }
+
+        public synchronized void addPkg(PkgUser pkg) {
+            if (mPkgs.add(pkg)) {
+                mTracker.onPersistableDirty(this);
+            }
+        }
+
+        public synchronized void removePkg(PkgUser pkg) {
+            if (mPkgs.remove(pkg)) {
+                mTracker.onPersistableDirty(this);
+            }
+        }
+
+        public synchronized Collection<PkgUser> getPkgs() {
+            return new ArraySet<>(mPkgs);
+        }
+
+        @Override
+        public String getFileName() {
+            return null;
+        }
+
+        public synchronized void writeTo(XmlSerializer out) throws IOException {
+            final int N = mPkgs.size();
+            for (int i = 0; i < N; i++) {
+                out.startTag(NAMESPACE, TAG_PKG);
+                out.text(mPkgs.valueAt(i).toString());
+                out.endTag(NAMESPACE, TAG_PKG);
+            }
+        }
+
+        public synchronized void readFrom(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            parser.next();
+            int depth = parser.getDepth();
+            while (parser.getDepth() >= depth) {
+                if (parser.getEventType() == XmlPullParser.START_TAG
+                        && TAG_PKG.equals(parser.getName())) {
+                    mPkgs.add(new PkgUser(parser.nextText()));
+                }
+                parser.next();
+            }
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
+            SliceAuthority other = (SliceAuthority) obj;
+            return Objects.equals(mAuthority, other.mAuthority)
+                    && Objects.equals(mPkgs, other.mPkgs);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("(%s: %s)", mAuthority, mPkgs.toString());
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index d012bba..18e842f 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -23,6 +23,7 @@
 import static android.app.ActivityManager.START_INTENT_NOT_RESOLVED;
 import static android.app.ActivityManager.START_NOT_VOICE_COMPATIBLE;
 import static android.app.ActivityManager.START_PERMISSION_DENIED;
+import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_SWITCHES_CANCELED;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
@@ -34,6 +35,7 @@
 
 import android.app.ActivityOptions;
 import android.app.IApplicationThread;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.WindowLayout;
@@ -74,6 +76,8 @@
 import com.android.server.am.LaunchParamsController.LaunchParamsModifier;
 import com.android.server.am.TaskRecord.TaskRecordFactory;
 
+import java.util.ArrayList;
+
 /**
  * Tests for the {@link ActivityStarter} class.
  *
@@ -301,13 +305,14 @@
                 anyBoolean(), any(), any(), any());
 
         // instrument the stack and task used.
-        final ActivityStack stack = spy(mService.mStackSupervisor.getDefaultDisplay().createStack(
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */));
-        final TaskRecord task =
-                spy(new TaskBuilder(mService.mStackSupervisor).setStack(stack).build());
+        final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final TaskRecord task = new TaskBuilder(mService.mStackSupervisor)
+                .setCreateStack(false)
+                .build();
 
         // supervisor needs a focused stack.
-        mService.mStackSupervisor.mFocusedStack = task.getStack();
+        mService.mStackSupervisor.mFocusedStack = stack;
 
         // use factory that only returns spy task.
         final TaskRecordFactory factory = mock(TaskRecordFactory.class);
@@ -322,14 +327,6 @@
         doReturn(stack).when(mService.mStackSupervisor)
                 .getLaunchStack(any(), any(), any(), anyBoolean(), anyInt());
 
-        // ignore the start request.
-        doNothing().when(stack)
-                .startActivityLocked(any(), any(), anyBoolean(), anyBoolean(), any());
-
-        // ignore requests to create window container.
-        doNothing().when(task).createWindowContainer(anyBoolean(), anyBoolean());
-
-
         final Intent intent = new Intent();
         intent.addFlags(launchFlags);
         intent.setComponent(ActivityBuilder.getDefaultComponent());
@@ -448,4 +445,30 @@
         // Ensure result is moving task to front.
         assertEquals(result, START_TASK_TO_FRONT);
     }
+
+    /**
+     * Tests activity is cleaned up properly in a task mode violation.
+     */
+    @Test
+    public void testTaskModeViolation() {
+        final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+        assertNoTasks(display);
+
+        final ActivityStarter starter = prepareStarter(0);
+
+        final LockTaskController lockTaskController = mService.getLockTaskController();
+        doReturn(true).when(lockTaskController).isLockTaskModeViolation(any());
+
+        final int result = starter.setReason("testTaskModeViolation").execute();
+
+        assertEquals(START_RETURN_LOCK_TASK_MODE_VIOLATION, result);
+        assertNoTasks(display);
+    }
+
+    private void assertNoTasks(ActivityDisplay display) {
+        for (int i = display.getChildCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = display.getChildAt(i);
+            assertTrue(stack.getAllTasks().isEmpty());
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 741901d..f5e61a1 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.spy;
 
+import android.app.ActivityOptions;
 import com.android.server.wm.DisplayWindowController;
 
 import org.junit.Rule;
@@ -51,6 +52,9 @@
 import android.support.test.InstrumentationRegistry;
 import android.testing.DexmakerShareClassLoaderRule;
 
+
+import com.android.internal.app.IVoiceInteractor;
+
 import com.android.server.AttributeCache;
 import com.android.server.wm.AppWindowContainerController;
 import com.android.server.wm.PinnedStackWindowController;
@@ -62,6 +66,7 @@
 import org.junit.Before;
 import org.mockito.MockitoAnnotations;
 
+
 /**
  * A base class to handle common operations in activity related unit tests.
  */
@@ -215,6 +220,7 @@
         private int mTaskId = 0;
         private int mUserId = 0;
         private IVoiceInteractionSession mVoiceSession;
+        private boolean mCreateStack = true;
 
         private ActivityStack mStack;
 
@@ -232,6 +238,15 @@
             return this;
         }
 
+        /**
+         * Set to {@code true} by default, set to {@code false} to prevent the task from
+         * automatically creating a parent stack.
+         */
+        TaskBuilder setCreateStack(boolean createStack) {
+            mCreateStack = createStack;
+            return this;
+        }
+
         TaskBuilder setVoiceSession(IVoiceInteractionSession session) {
             mVoiceSession = session;
             return this;
@@ -258,7 +273,7 @@
         }
 
         TaskRecord build() {
-            if (mStack == null) {
+            if (mStack == null && mCreateStack) {
                 mStack = mSupervisor.getDefaultDisplay().createStack(
                         WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
             }
@@ -276,17 +291,38 @@
             intent.setComponent(mComponent);
             intent.setFlags(mFlags);
 
-            final TaskRecord task = new TaskRecord(mSupervisor.mService, mTaskId, aInfo,
+            final TestTaskRecord task = new TestTaskRecord(mSupervisor.mService, mTaskId, aInfo,
                     intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/);
             task.userId = mUserId;
-            mSupervisor.setFocusStackUnchecked("test", mStack);
-            mStack.addTask(task, true, "creating test task");
-            task.setStack(mStack);
-            task.setWindowContainerController(mock(TaskWindowContainerController.class));
+
+            if (mStack != null) {
+                mSupervisor.setFocusStackUnchecked("test", mStack);
+                mStack.addTask(task, true, "creating test task");
+                task.setStack(mStack);
+                task.setWindowContainerController();
+            }
+
             task.touchActiveTime();
 
             return task;
         }
+
+        private static class TestTaskRecord extends TaskRecord {
+            TestTaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info,
+                       Intent _intent, IVoiceInteractionSession _voiceSession,
+                       IVoiceInteractor _voiceInteractor) {
+                super(service, _taskId, info, _intent, _voiceSession, _voiceInteractor);
+            }
+
+            @Override
+            void createWindowContainer(boolean onTop, boolean showForAllUsers) {
+                setWindowContainerController();
+            }
+
+            private void setWindowContainerController() {
+                setWindowContainerController(mock(TaskWindowContainerController.class));
+            }
+        }
     }
 
     /**
@@ -295,6 +331,7 @@
      */
     protected static class TestActivityManagerService extends ActivityManagerService {
         private ClientLifecycleManager mLifecycleManager;
+        private LockTaskController mLockTaskController;
 
         TestActivityManagerService(Context context) {
             super(context);
@@ -314,6 +351,14 @@
             return mLifecycleManager;
         }
 
+        public LockTaskController getLockTaskController() {
+            if (mLockTaskController == null) {
+                mLockTaskController = spy(super.getLockTaskController());
+            }
+
+            return mLockTaskController;
+        }
+
         void setLifecycleManager(ClientLifecycleManager manager) {
             mLifecycleManager = manager;
         }
@@ -444,7 +489,7 @@
     }
 
     /**
-     * Overrided of {@link ActivityStack} that tracks test metrics, such as the number of times a
+     * Overridden {@link ActivityStack} that tracks test metrics, such as the number of times a
      * method is called. Note that its functionality depends on the implementations of the
      * construction arguments.
      */
@@ -530,5 +575,11 @@
                     return super.supportsSplitScreenWindowingMode();
             }
         }
+
+        @Override
+        void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
+                                 boolean newTask, boolean keepCurTransition,
+                                 ActivityOptions options) {
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
index 36e4753..084f7e5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
@@ -297,7 +297,7 @@
                 intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
         final PersistableBundle extras2 = getExtras("testMyPackageSuspendedOnChangingExtras", 2,
                 "2", 0.2);
-        mPackageManager.setSuspendedPackageAppExtras(TEST_APP_PACKAGE_NAME, extras2);
+        suspendTestPackage(extras2, null, null);
         intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
         assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
                 ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceClientPermissionsTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceClientPermissionsTest.java
new file mode 100644
index 0000000..1efa415
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceClientPermissionsTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.server.slice;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.util.Xml.Encoding;
+
+import com.android.server.UiServiceTestCase;
+import com.android.server.slice.SlicePermissionManager.PkgUser;
+import com.android.server.slice.SliceClientPermissions.SliceAuthority;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class SliceClientPermissionsTest extends UiServiceTestCase {
+
+    @Test
+    public void testRemoveBasic() {
+        PkgUser pkg = new PkgUser("com.android.pkg", 0);
+        DirtyTracker tracker = mock(DirtyTracker.class);
+        SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+        Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority("com.android.pkg.slices").build();
+
+        PkgUser testPkg = new PkgUser("other", 2);
+
+        client.grantUri(base.buildUpon()
+                .appendPath("first")
+                .build(), testPkg);
+        client.revokeUri(base.buildUpon()
+                .appendPath("first")
+                .build(), testPkg);
+
+        assertFalse(client.hasPermission(base.buildUpon()
+                .appendPath("first")
+                .appendPath("third")
+                .build(), testPkg.getUserId()));
+
+        ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
+        assertEquals(0, authorities.get(0).getPaths().size());
+    }
+
+    @Test
+    public void testRemoveSubtrees() {
+        PkgUser pkg = new PkgUser("com.android.pkg", 0);
+        DirtyTracker tracker = mock(DirtyTracker.class);
+        SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+        Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority("com.android.pkg.slices").build();
+
+        PkgUser testPkg = new PkgUser("other", 2);
+
+        client.grantUri(base.buildUpon()
+                .appendPath("first")
+                .appendPath("second")
+                .build(), testPkg);
+        client.grantUri(base.buildUpon()
+                .appendPath("first")
+                .appendPath("third")
+                .build(), testPkg);
+        client.revokeUri(base.buildUpon()
+                .appendPath("first")
+                .build(), testPkg);
+
+        assertFalse(client.hasPermission(base.buildUpon()
+                .appendPath("first")
+                .appendPath("fourth")
+                .build(), testPkg.getUserId()));
+
+        ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
+        assertEquals(0, authorities.get(0).getPaths().size());
+    }
+
+    @Test
+    public void testAddConsolidate_addFirst() {
+        PkgUser pkg = new PkgUser("com.android.pkg", 0);
+        DirtyTracker tracker = mock(DirtyTracker.class);
+        SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+        Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority("com.android.pkg.slices").build();
+
+        PkgUser testPkg = new PkgUser("other", 2);
+
+        client.grantUri(base.buildUpon()
+                .appendPath("first")
+                .build(), testPkg);
+        client.grantUri(base.buildUpon()
+                .appendPath("first")
+                .appendPath("second")
+                .build(), testPkg);
+
+        assertTrue(client.hasPermission(base.buildUpon()
+                .appendPath("first")
+                .appendPath("third")
+                .build(), testPkg.getUserId()));
+
+        ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
+        assertEquals(1, authorities.get(0).getPaths().size());
+    }
+
+    @Test
+    public void testAddConsolidate_addSecond() {
+        PkgUser pkg = new PkgUser("com.android.pkg", 0);
+        DirtyTracker tracker = mock(DirtyTracker.class);
+        SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+        Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority("com.android.pkg.slices").build();
+
+        PkgUser testPkg = new PkgUser("other", 2);
+
+        client.grantUri(base.buildUpon()
+                .appendPath("first")
+                .appendPath("second")
+                .build(), testPkg);
+        client.grantUri(base.buildUpon()
+                .appendPath("first")
+                .build(), testPkg);
+
+        assertTrue(client.hasPermission(base.buildUpon()
+                .appendPath("first")
+                .appendPath("third")
+                .build(), testPkg.getUserId()));
+
+        ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
+        assertEquals(1, authorities.get(0).getPaths().size());
+    }
+
+    @Test
+    public void testDirty_addAuthority() {
+        PkgUser pkg = new PkgUser("com.android.pkg", 0);
+        DirtyTracker tracker = mock(DirtyTracker.class);
+        SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+
+        client.getOrCreateAuthority(new PkgUser("some_auth", 2), new PkgUser("com.pkg", 2));
+
+        verify(tracker).onPersistableDirty(eq(client));
+    }
+
+    @Test
+    public void testDirty_addPkg() {
+        PkgUser pkg = new PkgUser("com.android.pkg", 0);
+        DirtyTracker tracker = mock(DirtyTracker.class);
+        SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+
+        SliceAuthority auth = client.getOrCreateAuthority(
+                new PkgUser("some_auth", 2),
+                new PkgUser("com.pkg", 2));
+        clearInvocations(tracker);
+
+        auth.addPath(Arrays.asList("/something/"));
+
+        verify(tracker).onPersistableDirty(eq(client));
+    }
+
+    @Test
+    public void testCreation() {
+        SliceClientPermissions client = createClient();
+        ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
+        authorities.sort(Comparator.comparing(SliceAuthority::getAuthority));
+
+        assertEquals(2, authorities.size());
+        assertEquals("com.android.pkg", authorities.get(0).getAuthority());
+        assertEquals("com.android.pkg.slices", authorities.get(1).getAuthority());
+
+        assertEquals(1, authorities.get(0).getPaths().size());
+        assertEquals(2, authorities.get(1).getPaths().size());
+    }
+
+    @Test
+    public void testSerialization() throws XmlPullParserException, IOException {
+        SliceClientPermissions client = createClient();
+        client.setHasFullAccess(true);
+
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
+        serializer.setOutput(output, Encoding.UTF_8.name());
+
+        client.writeTo(serializer);
+        serializer.flush();
+
+        ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
+        XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+        parser.setInput(input, Encoding.UTF_8.name());
+
+        SliceClientPermissions deser = SliceClientPermissions.createFrom(parser,
+                mock(DirtyTracker.class));
+
+        assertEquivalent(client, deser);
+    }
+
+    private void assertEquivalent(SliceClientPermissions o1, SliceClientPermissions o2) {
+        assertEquals(o1.getPkg(), o2.getPkg());
+        ArrayList<SliceAuthority> a1 = new ArrayList<>(o1.getAuthorities());
+        ArrayList<SliceAuthority> a2 = new ArrayList<>(o2.getAuthorities());
+        a1.sort(Comparator.comparing(SliceAuthority::getAuthority));
+        a2.sort(Comparator.comparing(SliceAuthority::getAuthority));
+        assertEquals(a1, a2);
+    }
+
+    private static SliceClientPermissions createClient() {
+        PkgUser pkg = new PkgUser("com.android.pkg", 2);
+        DirtyTracker tracker = mock(DirtyTracker.class);
+        SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+
+        SliceAuthority auth = client.getOrCreateAuthority(
+                new PkgUser("com.android.pkg.slices", 3),
+                new PkgUser("com.android.pkg", 3));
+        auth.addPath(Arrays.asList("/something/"));
+        auth.addPath(Arrays.asList("/something/else"));
+
+        auth = client.getOrCreateAuthority(
+                new PkgUser("com.android.pkg", 3),
+                new PkgUser("com.pkg", 1));
+        auth.addPath(Arrays.asList("/somewhere"));
+        return client;
+    }
+
+}
\ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java
new file mode 100644
index 0000000..5443e73
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.server.slice;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.os.FileUtils;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.util.Log;
+import android.util.Xml.Encoding;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class SlicePermissionManagerTest extends UiServiceTestCase {
+
+    @Test
+    public void testBackup() throws XmlPullParserException, IOException {
+        File sliceDir = new File(mContext.getDataDir(), "system/slices");
+        Uri uri = new Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority("authority")
+                .path("something").build();
+        SlicePermissionManager permissions = new SlicePermissionManager(mContext,
+                TestableLooper.get(this).getLooper(), sliceDir);
+
+        permissions.grantFullAccess("com.android.mypkg", 10);
+        permissions.grantSliceAccess("com.android.otherpkg", 0, "com.android.lastpkg", 1, uri);
+
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
+        serializer.setOutput(output, Encoding.UTF_8.name());
+
+
+        TestableLooper.get(this).processAllMessages();
+        permissions.writeBackup(serializer);
+        serializer.flush();
+
+        ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
+        XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+        parser.setInput(input, Encoding.UTF_8.name());
+
+        permissions = new SlicePermissionManager(mContext,
+                TestableLooper.get(this).getLooper());
+        permissions.readRestore(parser);
+
+        assertTrue(permissions.hasFullAccess("com.android.mypkg", 10));
+        assertTrue(permissions.hasPermission("com.android.otherpkg", 0,
+                ContentProvider.maybeAddUserId(uri, 1)));
+        permissions.removePkg("com.android.lastpkg", 1);
+        assertFalse(permissions.hasPermission("com.android.otherpkg", 0,
+                ContentProvider.maybeAddUserId(uri, 1)));
+
+        // Cleanup.
+        assertTrue(FileUtils.deleteContentsAndDir(sliceDir));
+    }
+
+}
\ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceProviderPermissionsTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceProviderPermissionsTest.java
new file mode 100644
index 0000000..5775991
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceProviderPermissionsTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.server.slice;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.util.Xml.Encoding;
+
+import com.android.server.UiServiceTestCase;
+import com.android.server.slice.SlicePermissionManager.PkgUser;
+import com.android.server.slice.SliceProviderPermissions.SliceAuthority;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Comparator;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class SliceProviderPermissionsTest extends UiServiceTestCase {
+
+    @Test
+    public void testDirty_addAuthority() {
+        PkgUser pkg = new PkgUser("com.android.pkg", 0);
+        DirtyTracker tracker = mock(DirtyTracker.class);
+        SliceProviderPermissions provider = new SliceProviderPermissions(pkg, tracker);
+
+        provider.getOrCreateAuthority("some_auth");
+
+        verify(tracker).onPersistableDirty(eq(provider));
+    }
+
+    @Test
+    public void testDirty_addPkg() {
+        PkgUser pkg = new PkgUser("com.android.pkg", 0);
+        DirtyTracker tracker = mock(DirtyTracker.class);
+        SliceProviderPermissions provider = new SliceProviderPermissions(pkg, tracker);
+
+        SliceAuthority auth = provider.getOrCreateAuthority("some_auth");
+        clearInvocations(tracker);
+
+        auth.addPkg(new PkgUser("pkg", 0));
+
+        verify(tracker).onPersistableDirty(eq(provider));
+    }
+
+    @Test
+    public void testCreation() {
+        SliceProviderPermissions provider = createProvider();
+        ArrayList<SliceAuthority> authorities = new ArrayList<>(provider.getAuthorities());
+        authorities.sort(Comparator.comparing(SliceAuthority::getAuthority));
+
+        assertEquals(2, authorities.size());
+        assertEquals("com.android.pkg", authorities.get(0).getAuthority());
+        assertEquals("com.android.pkg.slices", authorities.get(1).getAuthority());
+
+        assertEquals(1, authorities.get(0).getPkgs().size());
+        assertEquals(2, authorities.get(1).getPkgs().size());
+    }
+
+    @Test
+    public void testSerialization() throws XmlPullParserException, IOException {
+        SliceProviderPermissions provider = createProvider();
+
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
+        serializer.setOutput(output, Encoding.UTF_8.name());
+
+        provider.writeTo(serializer);
+        serializer.flush();
+
+        ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
+        XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+        parser.setInput(input, Encoding.UTF_8.name());
+
+        SliceProviderPermissions deser = SliceProviderPermissions.createFrom(parser,
+                mock(DirtyTracker.class));
+
+        assertEquivalent(provider, deser);
+    }
+
+    private void assertEquivalent(SliceProviderPermissions o1, SliceProviderPermissions o2) {
+        assertEquals(o1.getPkg(), o2.getPkg());
+        assertEquals(o1.getAuthorities(), o2.getAuthorities());
+    }
+
+    private static SliceProviderPermissions createProvider() {
+        PkgUser pkg = new PkgUser("com.android.pkg", 2);
+        DirtyTracker tracker = mock(DirtyTracker.class);
+        SliceProviderPermissions provider = new SliceProviderPermissions(pkg, tracker);
+
+        SliceAuthority auth = provider.getOrCreateAuthority("com.android.pkg.slices");
+        auth.addPkg(new PkgUser("com.example.pkg", 0));
+        auth.addPkg(new PkgUser("example.pkg.com", 10));
+
+        auth = provider.getOrCreateAuthority("com.android.pkg");
+        auth.addPkg(new PkgUser("com.example.pkg", 2));
+        return provider;
+    }
+
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 7cb5ce4..3b9006c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5183,7 +5183,12 @@
      * {@link #AUTHTYPE_EAP_SIM}
      * @param data authentication challenge data, base64 encoded.
      * See 3GPP TS 31.102 7.1.2 for more details.
-     * @return the response of authentication, or null if not available
+     * @return the response of authentication. This value will be null in the following cases:
+     *   Authentication error, incorrect MAC
+     *   Authentication error, security context not supported
+     *   Key freshness failure
+     *   Authentication error, no memory space available
+     *   Authentication error, no memory space available in EFMUK
      */
     // TODO(b/73660190): This should probably require MODIFY_PHONE_STATE, not
     // READ_PRIVILEGED_PHONE_STATE. It certainly shouldn't reference the permission in Javadoc since
@@ -5204,7 +5209,13 @@
      * {@link #AUTHTYPE_EAP_SIM}
      * @param data authentication challenge data, base64 encoded.
      * See 3GPP TS 31.102 7.1.2 for more details.
-     * @return the response of authentication, or null if not available
+     * @return the response of authentication. This value will be null in the following cases only
+     * (see 3GPP TS 31.102 7.3.1):
+     *   Authentication error, incorrect MAC
+     *   Authentication error, security context not supported
+     *   Key freshness failure
+     *   Authentication error, no memory space available
+     *   Authentication error, no memory space available in EFMUK
      * @hide
      */
     public String getIccAuthentication(int subId, int appType, int authType, String data) {