Merge "Make DisplayCutout support waterfall insets"
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index 2ef4893..33f69a4 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -29,7 +29,9 @@
 import com.google.android.icing.proto.StatusProto;
 import com.google.android.icing.protobuf.InvalidProtocolBufferException;
 
+import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
@@ -73,7 +75,7 @@
      * </ul>
      *
      * <p>The following types of schema changes are not backwards-compatible. Supplying a schema
-     * with such changes will result in the provided callback being called with a {@link Throwable}
+     * with such changes will result in this call throwing an {@link IllegalSchemaException}
      * describing the incompatibility, and the previously set schema will remain active:
      * <ul>
      *     <li>Removal of an existing type
@@ -91,33 +93,44 @@
      *             REQUIRED} property.
      * </ul>
      *
-     * <p>If you need to make non-backwards-compatible changes as described above, you may set the
-     * {@code force} parameter to {@code true}. In this case, all documents which are not compatible
-     * with the new schema will be deleted.
-     *
-     * <p>This operation is performed asynchronously. On success, the provided callback will be
-     * called with {@code null}. On failure, the provided callback will be called with a
-     * {@link Throwable} describing the failure.
+     * <p>If you need to make non-backwards-compatible changes as described above, instead use the
+     * {@link #setSchema(List, boolean)} method with the {@code forceOverride} parameter set to
+     * {@code true}.
      *
      * <p>It is a no-op to set the same schema as has been previously set; this is handled
      * efficiently.
      *
      * @param schemas The schema configs for the types used by the calling app.
-     * @param force Whether to force the new schema to be applied even if there are incompatible
-     *     changes versus the previously set schema. Documents which are incompatible with the new
-     *     schema will be deleted.
-     * @param executor Executor on which to invoke the callback.
-     * @param callback Callback to receive errors resulting from setting the schema. If the
-     *                 operation succeeds, the callback will be invoked with {@code null}.
+     * @throws IllegalSchemaException If the provided schema is invalid, or is incompatible with the
+     *     previous schema.
      *
      * @hide
      */
     // TODO(b/143789408): linkify #put after that API is created
-    public void setSchema(
-            List<AppSearchSchema> schemas,
-            boolean force,
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<? super Throwable> callback) {
+    public void setSchema(@NonNull AppSearchSchema... schemas) {
+        setSchema(Arrays.asList(schemas), /*forceOverride=*/false);
+    }
+
+    /**
+     * Sets the schema being used by documents provided to the #put method.
+     *
+     * <p>This method is similar to {@link #setSchema(AppSearchSchema...)}, except for the
+     * {@code forceOverride} parameter. If a backwards-incompatible schema is specified but the
+     * {@code forceOverride} parameter is set to {@code true}, instead of throwing an
+     * {@link IllegalSchemaException}, all documents which are not compatible with the new schema
+     * will be deleted and the incompatible schema will be applied.
+     *
+     * @param schemas The schema configs for the types used by the calling app.
+     * @param forceOverride Whether to force the new schema to be applied even if there are
+     *     incompatible changes versus the previously set schema. Documents which are incompatible
+     *     with the new schema will be deleted.
+     * @throws IllegalSchemaException If the provided schema is invalid, or is incompatible with the
+     *     previous schema and the {@code forceOverride} parameter is set to {@code false}.
+     *
+     * @hide
+     */
+    // TODO(b/143789408): linkify #put after that API is created
+    public void setSchema(@NonNull List<AppSearchSchema> schemas, boolean forceOverride) {
         // Prepare the merged schema for transmission.
         SchemaProto.Builder schemaProtoBuilder = SchemaProto.newBuilder();
         for (AppSearchSchema schema : schemas) {
@@ -130,11 +143,11 @@
         byte[] schemaBytes = schemaProtoBuilder.build().toByteArray();
         AndroidFuture<Void> future = new AndroidFuture<>();
         try {
-            mService.setSchema(schemaBytes, force, future);
+            mService.setSchema(schemaBytes, forceOverride, future);
         } catch (RemoteException e) {
             future.completeExceptionally(e);
         }
-        future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
+        getFutureOrThrow(future);
     }
 
     /**
@@ -256,4 +269,21 @@
             future.completeExceptionally(e);
         }
     }
+
+    private static <T> T getFutureOrThrow(@NonNull AndroidFuture<T> future) {
+        try {
+            return future.get();
+        } catch (Throwable e) {
+            if (e instanceof ExecutionException) {
+                e = e.getCause();
+            }
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+            if (e instanceof Error) {
+                throw (Error) e;
+            }
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
index 6db65a4..194e43e 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
@@ -23,13 +23,13 @@
      * Sets the schema.
      *
      * @param schemaProto Serialized SchemaProto.
-     * @param force Whether to apply the new schema even if it is incompatible. All incompatible
-           documents will be deleted.
+     * @param forceOverride Whether to apply the new schema even if it is incompatible. All
+     *     incompatible documents will be deleted.
      * @param callback {@link AndroidFuture}&lt;{@link Void}&gt;. Will be completed with
-     *     {@code null} upon successful completion of the setSchema call, or completed exceptionally
-     *     if setSchema fails.
+     *     {@code null} upon successful completion of the setSchema call, or completed
+     *     exceptionally if setSchema fails.
      */
-    void setSchema(in byte[] schemaProto, boolean force, in AndroidFuture callback);
+    void setSchema(in byte[] schemaProto, boolean forceOverride, in AndroidFuture callback);
     void put(in byte[] documentBytes, in AndroidFuture callback);
     /**
      * Searches a document based on a given query string.
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index f8e010d..5d6d3f0 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -52,7 +52,7 @@
 
     private class Stub extends IAppSearchManager.Stub {
         @Override
-        public void setSchema(byte[] schemaBytes, boolean force, AndroidFuture callback) {
+        public void setSchema(byte[] schemaBytes, boolean forceOverride, AndroidFuture callback) {
             Preconditions.checkNotNull(schemaBytes);
             Preconditions.checkNotNull(callback);
             int callingUid = Binder.getCallingUidOrThrow();
@@ -61,7 +61,7 @@
             try {
                 SchemaProto schema = SchemaProto.parseFrom(schemaBytes);
                 AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
-                impl.setSchema(callingUid, schema, force);
+                impl.setSchema(callingUid, schema, forceOverride);
                 callback.complete(null);
             } catch (Throwable t) {
                 callback.completeExceptionally(t);
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
index e69fc8a..177c910 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
@@ -45,10 +45,10 @@
      *
      * @param callingUid The uid of the app calling AppSearch.
      * @param origSchema The schema to set for this app.
-     * @param force Whether to force-apply the schema even if it is incompatible. Documents which do
-     *     not comply with the new schema will be deleted.
+     * @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents
+     *     which do not comply with the new schema will be deleted.
      */
-    public void setSchema(int callingUid, @NonNull SchemaProto origSchema, boolean force) {
+    public void setSchema(int callingUid, @NonNull SchemaProto origSchema, boolean forceOverride) {
         // Rewrite schema type names to include the calling app's package and uid.
         String typePrefix = getTypePrefix(callingUid);
         SchemaProto.Builder schemaBuilder = origSchema.toBuilder();
diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp
index 8b03da3..09571a1 100644
--- a/apex/permission/framework/Android.bp
+++ b/apex/permission/framework/Android.bp
@@ -26,7 +26,13 @@
     srcs: [
         ":framework-permission-sources",
     ],
-    sdk_version: "system_current",
+    // TODO(b/146758669): Use "system_current" after nullability annotations are system APIs.
+    sdk_version: "core_current",
+    libs: [
+        "framework-annotations-lib",
+        // TODO(b/146758669): Remove this line after nullability annotations are system APIs.
+        "android_system_stubs_current",
+    ],
     apex_available: [
         "com.android.permission",
         "test_com.android.permission",
diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp
index 972b362..4172e95 100644
--- a/apex/permission/service/Android.bp
+++ b/apex/permission/service/Android.bp
@@ -12,13 +12,24 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-java_library {
-    name: "service-permission",
+filegroup {
+    name: "service-permission-sources",
     srcs: [
         "java/**/*.java",
     ],
-    sdk_version: "system_current",
+}
+
+java_library {
+    name: "service-permission",
+    srcs: [
+        ":service-permission-sources",
+    ],
+    // TODO(b/146758669): Use "system_current" after nullability annotations are system APIs.
+    sdk_version: "core_current",
     libs: [
+        "framework-annotations-lib",
+        // TODO(b/146758669): Remove this line after nullability annotations are system APIs.
+        "android_system_stubs_current",
         "framework-permission",
     ],
     apex_available: [
diff --git a/apex/permission/service/java/com/android/permission/persistence/IoUtils.java b/apex/permission/service/java/com/android/permission/persistence/IoUtils.java
new file mode 100644
index 0000000..0ae4460
--- /dev/null
+++ b/apex/permission/service/java/com/android/permission/persistence/IoUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 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.permission.persistence;
+
+import android.annotation.NonNull;
+
+/**
+ * Utility class for IO.
+ */
+public class IoUtils {
+
+    private IoUtils() {}
+
+    /**
+     * Close 'closeable' ignoring any exceptions.
+     */
+    public static void closeQuietly(@NonNull AutoCloseable closeable) {
+        try {
+            closeable.close();
+        } catch (Exception ignored) {
+            // Ignored.
+        }
+    }
+}
diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java
new file mode 100644
index 0000000..5f2d944
--- /dev/null
+++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 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.permission.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.UserHandle;
+
+/**
+ * Persistence for runtime permissions.
+ *
+ * TODO(b/147914847): Remove @hide when it becomes the default.
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER)
+public interface RuntimePermissionsPersistence {
+
+    /**
+     * Read the runtime permissions from persistence.
+     *
+     * This will perform I/O operations synchronously.
+     *
+     * @param user the user to read for
+     * @return the runtime permissions read
+     */
+    @Nullable
+    RuntimePermissionsState read(@NonNull UserHandle user);
+
+    /**
+     * Write the runtime permissions to persistence.
+     *
+     * This will perform I/O operations synchronously.
+     *
+     * @param runtimePermissions the runtime permissions to write
+     * @param user the user to write for
+     */
+    void write(@NonNull RuntimePermissionsState runtimePermissions, @NonNull UserHandle user);
+
+    /**
+     * Delete the runtime permissions from persistence.
+     *
+     * This will perform I/O operations synchronously.
+     *
+     * @param user the user to delete for
+     */
+    void delete(@NonNull UserHandle user);
+
+    /**
+     * Create a new instance of {@link RuntimePermissionsPersistence} implementation.
+     *
+     * @return the new instance.
+     */
+    @NonNull
+    static RuntimePermissionsPersistence createInstance() {
+        return new RuntimePermissionsPersistenceImpl();
+    }
+}
diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java
new file mode 100644
index 0000000..51b911a
--- /dev/null
+++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2020 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.permission.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Persistence implementation for runtime permissions.
+ *
+ * TODO(b/147914847): Remove @hide when it becomes the default.
+ * @hide
+ */
+public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPersistence {
+
+    private static final String LOG_TAG = RuntimePermissionsPersistenceImpl.class.getSimpleName();
+
+    private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml";
+
+    private static final String TAG_PACKAGE = "package";
+    private static final String TAG_PERMISSION = "permission";
+    private static final String TAG_RUNTIME_PERMISSIONS = "runtime-permissions";
+    private static final String TAG_SHARED_USER = "shared-user";
+
+    private static final String ATTRIBUTE_FINGERPRINT = "fingerprint";
+    private static final String ATTRIBUTE_FLAGS = "flags";
+    private static final String ATTRIBUTE_GRANTED = "granted";
+    private static final String ATTRIBUTE_NAME = "name";
+    private static final String ATTRIBUTE_VERSION = "version";
+
+    @Nullable
+    @Override
+    public RuntimePermissionsState read(@NonNull UserHandle user) {
+        File file = getFile(user);
+        try (FileInputStream inputStream = new AtomicFile(file).openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(inputStream, null);
+            return parseXml(parser);
+        } catch (FileNotFoundException e) {
+            Log.i(LOG_TAG, "runtime-permissions.xml not found");
+            return null;
+        } catch (XmlPullParserException | IOException e) {
+            throw new IllegalStateException("Failed to read runtime-permissions.xml: " + file , e);
+        }
+    }
+
+    @NonNull
+    private static RuntimePermissionsState parseXml(@NonNull XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        int type;
+        int depth;
+        int innerDepth = parser.getDepth() + 1;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (parser.getName().equals(TAG_RUNTIME_PERMISSIONS)) {
+                return parseRuntimePermissions(parser);
+            }
+        }
+        throw new IllegalStateException("Missing <" + TAG_RUNTIME_PERMISSIONS
+                + "> in runtime-permissions.xml");
+    }
+
+    @NonNull
+    private static RuntimePermissionsState parseRuntimePermissions(@NonNull XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        String versionValue = parser.getAttributeValue(null, ATTRIBUTE_VERSION);
+        int version = versionValue != null ? Integer.parseInt(versionValue)
+                : RuntimePermissionsState.NO_VERSION;
+        String fingerprint = parser.getAttributeValue(null, ATTRIBUTE_FINGERPRINT);
+
+        Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
+                new ArrayMap<>();
+        Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
+                new ArrayMap<>();
+        int type;
+        int depth;
+        int innerDepth = parser.getDepth() + 1;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            switch (parser.getName()) {
+                case TAG_PACKAGE: {
+                    String packageName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+                    List<RuntimePermissionsState.PermissionState> permissions = parsePermissions(
+                            parser);
+                    packagePermissions.put(packageName, permissions);
+                    break;
+                }
+                case TAG_SHARED_USER: {
+                    String sharedUserName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+                    List<RuntimePermissionsState.PermissionState> permissions = parsePermissions(
+                            parser);
+                    sharedUserPermissions.put(sharedUserName, permissions);
+                    break;
+                }
+            }
+        }
+
+        return new RuntimePermissionsState(version, fingerprint, packagePermissions,
+                sharedUserPermissions);
+    }
+
+    @NonNull
+    private static List<RuntimePermissionsState.PermissionState> parsePermissions(
+            @NonNull XmlPullParser parser) throws IOException, XmlPullParserException {
+        List<RuntimePermissionsState.PermissionState> permissions = new ArrayList<>();
+        int type;
+        int depth;
+        int innerDepth = parser.getDepth() + 1;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (parser.getName().equals(TAG_PERMISSION)) {
+                String name = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+                boolean granted = Boolean.parseBoolean(parser.getAttributeValue(null,
+                        ATTRIBUTE_GRANTED));
+                int flags = Integer.parseInt(parser.getAttributeValue(null,
+                        ATTRIBUTE_FLAGS), 16);
+                RuntimePermissionsState.PermissionState permission =
+                        new RuntimePermissionsState.PermissionState(name, granted, flags);
+                permissions.add(permission);
+            }
+        }
+        return permissions;
+    }
+
+    @Override
+    public void write(@NonNull RuntimePermissionsState runtimePermissions,
+            @NonNull UserHandle user) {
+        File file = getFile(user);
+        AtomicFile atomicFile = new AtomicFile(file);
+        FileOutputStream outputStream = null;
+        try {
+            outputStream = atomicFile.startWrite();
+
+            XmlSerializer serializer = Xml.newSerializer();
+            serializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            serializer.startDocument(null, true);
+
+            serializeRuntimePermissions(serializer, runtimePermissions);
+
+            serializer.endDocument();
+            atomicFile.finishWrite(outputStream);
+        } catch (Exception e) {
+            Log.wtf(LOG_TAG, "Failed to write runtime-permissions.xml, restoring backup: " + file,
+                    e);
+            atomicFile.failWrite(outputStream);
+        } finally {
+            IoUtils.closeQuietly(outputStream);
+        }
+    }
+
+    private static void serializeRuntimePermissions(@NonNull XmlSerializer serializer,
+            @NonNull RuntimePermissionsState runtimePermissions) throws IOException {
+        serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);
+
+        int version = runtimePermissions.getVersion();
+        serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
+        String fingerprint = runtimePermissions.getFingerprint();
+        if (fingerprint != null) {
+            serializer.attribute(null, ATTRIBUTE_FINGERPRINT, fingerprint);
+        }
+
+        for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
+                : runtimePermissions.getPackagePermissions().entrySet()) {
+            String packageName = entry.getKey();
+            List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
+
+            serializer.startTag(null, TAG_PACKAGE);
+            serializer.attribute(null, ATTRIBUTE_NAME, packageName);
+            serializePermissions(serializer, permissions);
+            serializer.endTag(null, TAG_PACKAGE);
+        }
+
+        for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
+                : runtimePermissions.getSharedUserPermissions().entrySet()) {
+            String sharedUserName = entry.getKey();
+            List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
+
+            serializer.startTag(null, TAG_SHARED_USER);
+            serializer.attribute(null, ATTRIBUTE_NAME, sharedUserName);
+            serializePermissions(serializer, permissions);
+            serializer.endTag(null, TAG_SHARED_USER);
+        }
+
+        serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);
+    }
+
+    private static void serializePermissions(@NonNull XmlSerializer serializer,
+            @NonNull List<RuntimePermissionsState.PermissionState> permissions) throws IOException {
+        int permissionsSize = permissions.size();
+        for (int i = 0; i < permissionsSize; i++) {
+            RuntimePermissionsState.PermissionState permissionState = permissions.get(i);
+
+            serializer.startTag(null, TAG_PERMISSION);
+            serializer.attribute(null, ATTRIBUTE_NAME, permissionState.getName());
+            serializer.attribute(null, ATTRIBUTE_GRANTED, Boolean.toString(
+                    permissionState.isGranted()));
+            serializer.attribute(null, ATTRIBUTE_FLAGS, Integer.toHexString(
+                    permissionState.getFlags()));
+            serializer.endTag(null, TAG_PERMISSION);
+        }
+    }
+
+    @Override
+    public void delete(@NonNull UserHandle user) {
+        getFile(user).delete();
+    }
+
+    @NonNull
+    private static File getFile(@NonNull UserHandle user) {
+        // TODO: Use an API for this.
+        File dataDirectory = new File("/data/misc_de/" + user.getIdentifier()
+                + "/apexdata/com.android.permission");
+        return new File(dataDirectory, RUNTIME_PERMISSIONS_FILE_NAME);
+    }
+}
diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java
new file mode 100644
index 0000000..2a939e5
--- /dev/null
+++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 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.permission.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * State of all runtime permissions.
+ *
+ * TODO(b/147914847): Remove @hide when it becomes the default.
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER)
+public final class RuntimePermissionsState {
+
+    /**
+     * Special value for {@link #mVersion} to indicate that no version was read.
+     */
+    public static final int NO_VERSION = -1;
+
+    /**
+     * The version of the runtime permissions.
+     */
+    private final int mVersion;
+
+    /**
+     * The fingerprint of the runtime permissions.
+     */
+    @Nullable
+    private final String mFingerprint;
+
+    /**
+     * The runtime permissions by packages.
+     */
+    @NonNull
+    private final Map<String, List<PermissionState>> mPackagePermissions;
+
+    /**
+     * The runtime permissions by shared users.
+     */
+    @NonNull
+    private final Map<String, List<PermissionState>> mSharedUserPermissions;
+
+    public RuntimePermissionsState(int version, @Nullable String fingerprint,
+            @NonNull Map<String, List<PermissionState>> packagePermissions,
+            @NonNull Map<String, List<PermissionState>> sharedUserPermissions) {
+        mVersion = version;
+        mFingerprint = fingerprint;
+        mPackagePermissions = packagePermissions;
+        mSharedUserPermissions = sharedUserPermissions;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    @Nullable
+    public String getFingerprint() {
+        return mFingerprint;
+    }
+
+    @NonNull
+    public Map<String, List<PermissionState>> getPackagePermissions() {
+        return mPackagePermissions;
+    }
+
+    @NonNull
+    public Map<String, List<PermissionState>> getSharedUserPermissions() {
+        return mSharedUserPermissions;
+    }
+
+    /**
+     * State of a single permission.
+     */
+    public static class PermissionState {
+
+        /**
+         * Name of the permission.
+         */
+        @NonNull
+        private final String mName;
+
+        /**
+         * Whether the permission is granted.
+         */
+        private final boolean mGranted;
+
+        /**
+         * Flags of the permission.
+         */
+        private final int mFlags;
+
+        public PermissionState(@NonNull String name, boolean granted, int flags) {
+            mName = name;
+            mGranted = granted;
+            mFlags = flags;
+        }
+
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        public boolean isGranted() {
+            return mGranted;
+        }
+
+        public int getFlags() {
+            return mFlags;
+        }
+    }
+}
diff --git a/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java b/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java
new file mode 100644
index 0000000..63c8eed
--- /dev/null
+++ b/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 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.role.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.UserHandle;
+
+/**
+ * Persistence for roles.
+ *
+ * TODO(b/147914847): Remove @hide when it becomes the default.
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER)
+public interface RolesPersistence {
+
+    /**
+     * Read the roles from persistence.
+     *
+     * This will perform I/O operations synchronously.
+     *
+     * @param user the user to read for
+     * @return the roles read
+     */
+    @Nullable
+    RolesState read(@NonNull UserHandle user);
+
+    /**
+     * Write the roles to persistence.
+     *
+     * This will perform I/O operations synchronously.
+     *
+     * @param roles the roles to write
+     * @param user the user to write for
+     */
+    void write(@NonNull RolesState roles, @NonNull UserHandle user);
+
+    /**
+     * Delete the roles from persistence.
+     *
+     * This will perform I/O operations synchronously.
+     *
+     * @param user the user to delete for
+     */
+    void delete(@NonNull UserHandle user);
+
+    /**
+     * Create a new instance of {@link RolesPersistence} implementation.
+     *
+     * @return the new instance.
+     */
+    @NonNull
+    static RolesPersistence createInstance() {
+        return new RolesPersistenceImpl();
+    }
+}
diff --git a/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java b/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java
new file mode 100644
index 0000000..5061742
--- /dev/null
+++ b/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2020 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.role.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.permission.persistence.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Persistence implementation for roles.
+ *
+ * TODO(b/147914847): Remove @hide when it becomes the default.
+ * @hide
+ */
+public class RolesPersistenceImpl implements RolesPersistence {
+
+    private static final String LOG_TAG = RolesPersistenceImpl.class.getSimpleName();
+
+    private static final String ROLES_FILE_NAME = "roles.xml";
+
+    private static final String TAG_ROLES = "roles";
+    private static final String TAG_ROLE = "role";
+    private static final String TAG_HOLDER = "holder";
+
+    private static final String ATTRIBUTE_VERSION = "version";
+    private static final String ATTRIBUTE_NAME = "name";
+    private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash";
+
+    @Nullable
+    @Override
+    public RolesState read(@NonNull UserHandle user) {
+        File file = getFile(user);
+        try (FileInputStream inputStream = new AtomicFile(file).openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(inputStream, null);
+            return parseXml(parser);
+        } catch (FileNotFoundException e) {
+            Log.i(LOG_TAG, "roles.xml not found");
+            return null;
+        } catch (XmlPullParserException | IOException e) {
+            throw new IllegalStateException("Failed to read roles.xml: " + file , e);
+        }
+    }
+
+    @NonNull
+    private static RolesState parseXml(@NonNull XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        int type;
+        int depth;
+        int innerDepth = parser.getDepth() + 1;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (parser.getName().equals(TAG_ROLES)) {
+                return parseRoles(parser);
+            }
+        }
+        throw new IllegalStateException("Missing <" + TAG_ROLES + "> in roles.xml");
+    }
+
+    @NonNull
+    private static RolesState parseRoles(@NonNull XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        int version = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION));
+        String packagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH);
+
+        Map<String, Set<String>> roles = new ArrayMap<>();
+        int type;
+        int depth;
+        int innerDepth = parser.getDepth() + 1;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (parser.getName().equals(TAG_ROLE)) {
+                String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+                Set<String> roleHolders = parseRoleHolders(parser);
+                roles.put(roleName, roleHolders);
+            }
+        }
+
+        return new RolesState(version, packagesHash, roles);
+    }
+
+    @NonNull
+    private static Set<String> parseRoleHolders(@NonNull XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        Set<String> roleHolders = new ArraySet<>();
+        int type;
+        int depth;
+        int innerDepth = parser.getDepth() + 1;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (parser.getName().equals(TAG_HOLDER)) {
+                String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+                roleHolders.add(roleHolder);
+            }
+        }
+        return roleHolders;
+    }
+
+    @Override
+    public void write(@NonNull RolesState roles,
+            @NonNull UserHandle user) {
+        File file = getFile(user);
+        AtomicFile atomicFile = new AtomicFile(file);
+        FileOutputStream outputStream = null;
+        try {
+            outputStream = atomicFile.startWrite();
+
+            XmlSerializer serializer = Xml.newSerializer();
+            serializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            serializer.startDocument(null, true);
+
+            serializeRoles(serializer, roles);
+
+            serializer.endDocument();
+            atomicFile.finishWrite(outputStream);
+        } catch (Exception e) {
+            Log.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup: " + file,
+                    e);
+            atomicFile.failWrite(outputStream);
+        } finally {
+            IoUtils.closeQuietly(outputStream);
+        }
+    }
+
+    private static void serializeRoles(@NonNull XmlSerializer serializer,
+            @NonNull RolesState roles) throws IOException {
+        serializer.startTag(null, TAG_ROLES);
+
+        int version = roles.getVersion();
+        serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
+        String packagesHash = roles.getPackagesHash();
+        if (packagesHash != null) {
+            serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash);
+        }
+
+        for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) {
+            String roleName = entry.getKey();
+            Set<String> roleHolders = entry.getValue();
+
+            serializer.startTag(null, TAG_ROLE);
+            serializer.attribute(null, ATTRIBUTE_NAME, roleName);
+            serializeRoleHolders(serializer, roleHolders);
+            serializer.endTag(null, TAG_ROLE);
+        }
+
+        serializer.endTag(null, TAG_ROLES);
+    }
+
+    private static void serializeRoleHolders(@NonNull XmlSerializer serializer,
+            @NonNull Set<String> roleHolders) throws IOException {
+        for (String roleHolder : roleHolders) {
+            serializer.startTag(null, TAG_HOLDER);
+            serializer.attribute(null, ATTRIBUTE_NAME, roleHolder);
+            serializer.endTag(null, TAG_HOLDER);
+        }
+    }
+
+    @Override
+    public void delete(@NonNull UserHandle user) {
+        getFile(user).delete();
+    }
+
+    @NonNull
+    private static File getFile(@NonNull UserHandle user) {
+        // TODO: Use an API for this.
+        File dataDirectory = new File("/data/misc_de/" + user.getIdentifier()
+                + "/apexdata/com.android.permission");
+        return new File(dataDirectory, ROLES_FILE_NAME);
+    }
+}
diff --git a/apex/permission/service/java/com/android/role/persistence/RolesState.java b/apex/permission/service/java/com/android/role/persistence/RolesState.java
new file mode 100644
index 0000000..bff980e
--- /dev/null
+++ b/apex/permission/service/java/com/android/role/persistence/RolesState.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 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.role.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * State of all roles.
+ *
+ * TODO(b/147914847): Remove @hide when it becomes the default.
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER)
+public final class RolesState {
+
+    /**
+     * The version of the roles.
+     */
+    private final int mVersion;
+
+    /**
+     * The hash of all packages in the system.
+     */
+    @Nullable
+    private final String mPackagesHash;
+
+    /**
+     * The roles.
+     */
+    @NonNull
+    private final Map<String, Set<String>> mRoles;
+
+    public RolesState(int version, @Nullable String packagesHash,
+            @NonNull Map<String, Set<String>> roles) {
+        mVersion = version;
+        mPackagesHash = packagesHash;
+        mRoles = roles;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    @Nullable
+    public String getPackagesHash() {
+        return mPackagesHash;
+    }
+
+    @NonNull
+    public Map<String, Set<String>> getRoles() {
+        return mRoles;
+    }
+}
diff --git a/apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java b/apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java
deleted file mode 100644
index a534e22..0000000
--- a/apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2020 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.permission;
-
-/**
- * Persistence for runtime permissions.
- */
-public class RuntimePermissionPersistence {}
diff --git a/api/current.txt b/api/current.txt
index 0450276..175c89b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -30508,6 +30508,21 @@
     field public CharSequence venueName;
   }
 
+  public final class SoftApConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.net.MacAddress getBssid();
+    method @Nullable public String getPassphrase();
+    method public int getSecurityType();
+    method @Nullable public String getSsid();
+    method public boolean isHiddenSsid();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApConfiguration> CREATOR;
+    field public static final int SECURITY_TYPE_OPEN = 0; // 0x0
+    field public static final int SECURITY_TYPE_WPA2_PSK = 1; // 0x1
+    field public static final int SECURITY_TYPE_WPA3_SAE = 3; // 0x3
+    field public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2; // 0x2
+  }
+
   public enum SupplicantState implements android.os.Parcelable {
     method public int describeContents();
     method public static boolean isValidState(android.net.wifi.SupplicantState);
@@ -30842,7 +30857,8 @@
 
   public class WifiManager.LocalOnlyHotspotReservation implements java.lang.AutoCloseable {
     method public void close();
-    method public android.net.wifi.WifiConfiguration getWifiConfiguration();
+    method @NonNull public android.net.wifi.SoftApConfiguration getSoftApConfiguration();
+    method @Deprecated @Nullable public android.net.wifi.WifiConfiguration getWifiConfiguration();
   }
 
   public class WifiManager.MulticastLock {
diff --git a/api/system-current.txt b/api/system-current.txt
index 9c53f80..3983b51 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6113,29 +6113,17 @@
   }
 
   public final class SoftApConfiguration implements android.os.Parcelable {
-    method public int describeContents();
     method @NonNull public java.util.List<android.net.MacAddress> getAllowedClientList();
     method public int getBand();
     method @NonNull public java.util.List<android.net.MacAddress> getBlockedClientList();
-    method @Nullable public android.net.MacAddress getBssid();
     method public int getChannel();
     method public int getMaxNumberOfClients();
-    method @Nullable public String getPassphrase();
-    method public int getSecurityType();
     method public int getShutdownTimeoutMillis();
-    method @Nullable public String getSsid();
     method public boolean isClientControlByUserEnabled();
-    method public boolean isHiddenSsid();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int BAND_2GHZ = 1; // 0x1
     field public static final int BAND_5GHZ = 2; // 0x2
     field public static final int BAND_6GHZ = 4; // 0x4
     field public static final int BAND_ANY = 7; // 0x7
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApConfiguration> CREATOR;
-    field public static final int SECURITY_TYPE_OPEN = 0; // 0x0
-    field public static final int SECURITY_TYPE_WPA2_PSK = 1; // 0x1
-    field public static final int SECURITY_TYPE_WPA3_SAE = 3; // 0x3
-    field public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2; // 0x2
   }
 
   public static final class SoftApConfiguration.Builder {
@@ -10359,6 +10347,19 @@
     field public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
   }
 
+  public final class PinResult implements android.os.Parcelable {
+    ctor public PinResult(int, int);
+    method public int describeContents();
+    method public int getAttemptsRemaining();
+    method @NonNull public static android.telephony.PinResult getDefaultFailedResult();
+    method public int getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PinResult> CREATOR;
+    field public static final int PIN_RESULT_TYPE_FAILURE = 2; // 0x2
+    field public static final int PIN_RESULT_TYPE_INCORRECT = 1; // 0x1
+    field public static final int PIN_RESULT_TYPE_SUCCESS = 0; // 0x0
+  }
+
   public final class PreciseCallState implements android.os.Parcelable {
     ctor public PreciseCallState(int, int, int, int, int);
     method public int describeContents();
@@ -10842,9 +10843,11 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoiceActivationState(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void shutdownAllRadios();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPin(String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyPinReportPinResult(@NonNull String);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPuk(String, String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyPukReportPinResult(@NonNull String, @NonNull String);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void toggleRadioOnOff();
     method public void updateServiceLocation();
diff --git a/services/Android.bp b/services/Android.bp
index 5afed6c..914ea28 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -6,7 +6,7 @@
 }
 
 filegroup {
-    name: "services-sources",
+    name: "services-stub-sources",
     srcs: [
         ":services.core-sources",
         ":services.accessibility-sources",
@@ -29,6 +29,7 @@
         ":services.usage-sources",
         ":services.usb-sources",
         ":services.voiceinteraction-sources",
+        ":service-permission-sources",
     ],
     visibility: ["//visibility:private"],
 }
@@ -110,7 +111,7 @@
 
 droidstubs {
     name: "services-stubs.sources",
-    srcs: [":services-sources"],
+    srcs: [":services-stub-sources"],
     installable: false,
     // TODO: remove the --hide options below
     args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES,process=android.annotation.SystemApi.Process.SYSTEM_SERVER\\)" +
diff --git a/services/api/current.txt b/services/api/current.txt
index 18e38b1..8a82e61 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -1,4 +1,49 @@
 // Signature format: 2.0
+package com.android.permission.persistence {
+
+  public interface RuntimePermissionsPersistence {
+    method @NonNull public static com.android.permission.persistence.RuntimePermissionsPersistence createInstance();
+    method public void delete(@NonNull android.os.UserHandle);
+    method @Nullable public com.android.permission.persistence.RuntimePermissionsState read(@NonNull android.os.UserHandle);
+    method public void write(@NonNull com.android.permission.persistence.RuntimePermissionsState, @NonNull android.os.UserHandle);
+  }
+
+  public final class RuntimePermissionsState {
+    ctor public RuntimePermissionsState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>);
+    method @Nullable public String getFingerprint();
+    method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getPackagePermissions();
+    method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getSharedUserPermissions();
+    method public int getVersion();
+    field public static final int NO_VERSION = -1; // 0xffffffff
+  }
+
+  public static class RuntimePermissionsState.PermissionState {
+    ctor public RuntimePermissionsState.PermissionState(@NonNull String, boolean, int);
+    method public int getFlags();
+    method @NonNull public String getName();
+    method public boolean isGranted();
+  }
+
+}
+
+package com.android.role.persistence {
+
+  public interface RolesPersistence {
+    method @NonNull public static com.android.role.persistence.RolesPersistence createInstance();
+    method public void delete(@NonNull android.os.UserHandle);
+    method @Nullable public com.android.role.persistence.RolesState read(@NonNull android.os.UserHandle);
+    method public void write(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle);
+  }
+
+  public final class RolesState {
+    ctor public RolesState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>>);
+    method @Nullable public String getPackagesHash();
+    method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRoles();
+    method public int getVersion();
+  }
+
+}
+
 package com.android.server {
 
   public abstract class SystemService {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index b2fba73..02d4f94 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -90,6 +90,7 @@
     ],
 
     libs: [
+        "services-stubs",
         "services.net",
         "android.hardware.light-V2.0-java",
         "android.hardware.power-V1.0-java",
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4f18cb4..5d948b2 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -92,6 +92,8 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
+import com.android.permission.persistence.RuntimePermissionsPersistence;
+import com.android.permission.persistence.RuntimePermissionsState;
 import com.android.server.LocalServices;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.permission.BasePermission;
@@ -5096,6 +5098,9 @@
         private static final int UPGRADE_VERSION = -1;
         private static final int INITIAL_VERSION = 0;
 
+        private final RuntimePermissionsPersistence mPersistence =
+                RuntimePermissionsPersistence.createInstance();
+
         private final Handler mHandler = new MyHandler();
 
         private final Object mPersistenceLock;
@@ -5185,98 +5190,72 @@
         }
 
         private void writePermissionsSync(int userId) {
-            AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId),
-                    "package-perms-" + userId);
-
-            ArrayMap<String, List<PermissionState>> permissionsForPackage = new ArrayMap<>();
-            ArrayMap<String, List<PermissionState>> permissionsForSharedUser = new ArrayMap<>();
-
+            RuntimePermissionsState runtimePermissions;
             synchronized (mPersistenceLock) {
                 mWriteScheduled.delete(userId);
 
-                final int packageCount = mPackages.size();
-                for (int i = 0; i < packageCount; i++) {
+                int version = mVersions.get(userId, INITIAL_VERSION);
+
+                String fingerprint = mFingerprints.get(userId);
+
+                Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
+                        new ArrayMap<>();
+                int packagesSize = mPackages.size();
+                for (int i = 0; i < packagesSize; i++) {
                     String packageName = mPackages.keyAt(i);
                     PackageSetting packageSetting = mPackages.valueAt(i);
                     if (packageSetting.sharedUser == null) {
-                        PermissionsState permissionsState = packageSetting.getPermissionsState();
-                        List<PermissionState> permissionsStates = permissionsState
-                                .getRuntimePermissionStates(userId);
-                        if (!permissionsStates.isEmpty()) {
-                            permissionsForPackage.put(packageName, permissionsStates);
+                        List<RuntimePermissionsState.PermissionState> permissions =
+                                getPermissionsFromPermissionsState(
+                                        packageSetting.getPermissionsState(), userId);
+                        if (permissions != null) {
+                            packagePermissions.put(packageName, permissions);
                         }
                     }
                 }
 
-                final int sharedUserCount = mSharedUsers.size();
-                for (int i = 0; i < sharedUserCount; i++) {
+                Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
+                        new ArrayMap<>();
+                final int sharedUsersSize = mSharedUsers.size();
+                for (int i = 0; i < sharedUsersSize; i++) {
                     String sharedUserName = mSharedUsers.keyAt(i);
-                    SharedUserSetting sharedUser = mSharedUsers.valueAt(i);
-                    PermissionsState permissionsState = sharedUser.getPermissionsState();
-                    List<PermissionState> permissionsStates = permissionsState
-                            .getRuntimePermissionStates(userId);
-                    if (!permissionsStates.isEmpty()) {
-                        permissionsForSharedUser.put(sharedUserName, permissionsStates);
+                    SharedUserSetting sharedUserSetting = mSharedUsers.valueAt(i);
+                    List<RuntimePermissionsState.PermissionState> permissions =
+                            getPermissionsFromPermissionsState(
+                                    sharedUserSetting.getPermissionsState(), userId);
+                    if (permissions != null) {
+                        sharedUserPermissions.put(sharedUserName, permissions);
                     }
                 }
+
+                runtimePermissions = new RuntimePermissionsState(version, fingerprint,
+                        packagePermissions, sharedUserPermissions);
             }
 
-            FileOutputStream out = null;
-            try {
-                out = destination.startWrite();
+            mPersistence.write(runtimePermissions, UserHandle.of(userId));
+        }
 
-                XmlSerializer serializer = Xml.newSerializer();
-                serializer.setOutput(out, StandardCharsets.UTF_8.name());
-                serializer.setFeature(
-                        "http://xmlpull.org/v1/doc/features.html#indent-output", true);
-                serializer.startDocument(null, true);
-
-                serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);
-
-                final int version = mVersions.get(userId, INITIAL_VERSION);
-                serializer.attribute(null, ATTR_VERSION, Integer.toString(version));
-
-                String fingerprint = mFingerprints.get(userId);
-                if (fingerprint != null) {
-                    serializer.attribute(null, ATTR_FINGERPRINT, fingerprint);
-                }
-
-                final int packageCount = permissionsForPackage.size();
-                for (int i = 0; i < packageCount; i++) {
-                    String packageName = permissionsForPackage.keyAt(i);
-                    List<PermissionState> permissionStates = permissionsForPackage.valueAt(i);
-                    serializer.startTag(null, TAG_PACKAGE);
-                    serializer.attribute(null, ATTR_NAME, packageName);
-                    writePermissions(serializer, permissionStates);
-                    serializer.endTag(null, TAG_PACKAGE);
-                }
-
-                final int sharedUserCount = permissionsForSharedUser.size();
-                for (int i = 0; i < sharedUserCount; i++) {
-                    String packageName = permissionsForSharedUser.keyAt(i);
-                    List<PermissionState> permissionStates = permissionsForSharedUser.valueAt(i);
-                    serializer.startTag(null, TAG_SHARED_USER);
-                    serializer.attribute(null, ATTR_NAME, packageName);
-                    writePermissions(serializer, permissionStates);
-                    serializer.endTag(null, TAG_SHARED_USER);
-                }
-
-                serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);
-
-                serializer.endDocument();
-                destination.finishWrite(out);
-
-                if (Build.FINGERPRINT.equals(fingerprint)) {
-                    mDefaultPermissionsGranted.put(userId, true);
-                }
-            // Any error while writing is fatal.
-            } catch (Throwable t) {
-                Slog.wtf(PackageManagerService.TAG,
-                        "Failed to write settings, restoring backup", t);
-                destination.failWrite(out);
-            } finally {
-                IoUtils.closeQuietly(out);
+        @Nullable
+        private List<RuntimePermissionsState.PermissionState> getPermissionsFromPermissionsState(
+                @NonNull PermissionsState permissionsState, @UserIdInt int userId) {
+            List<PermissionState> permissionStates = permissionsState.getRuntimePermissionStates(
+                    userId);
+            if (permissionStates.isEmpty()) {
+                return null;
             }
+
+            List<RuntimePermissionsState.PermissionState> permissions =
+                    new ArrayList<>();
+            int permissionStatesSize = permissionStates.size();
+            for (int i = 0; i < permissionStatesSize; i++) {
+                PermissionState permissionState = permissionStates.get(i);
+
+                RuntimePermissionsState.PermissionState permission =
+                        new RuntimePermissionsState.PermissionState(permissionState.getName(),
+                                permissionState.isGranted(), permissionState.getFlags());
+                permissions.add(permission);
+            }
+            return permissions;
         }
 
         @GuardedBy("Settings.this.mLock")
@@ -5311,11 +5290,88 @@
         }
 
         public void deleteUserRuntimePermissionsFile(int userId) {
-            getUserRuntimePermissionsFile(userId).delete();
+            mPersistence.delete(UserHandle.of(userId));
         }
 
         @GuardedBy("Settings.this.mLock")
         public void readStateForUserSyncLPr(int userId) {
+            RuntimePermissionsState runtimePermissions = mPersistence.read(UserHandle.of(userId));
+            if (runtimePermissions == null) {
+                readLegacyStateForUserSyncLPr(userId);
+                writePermissionsForUserAsyncLPr(userId);
+                return;
+            }
+
+            // If the runtime permissions file exists but the version is not set this is
+            // an upgrade from P->Q. Hence mark it with the special UPGRADE_VERSION.
+            int version = runtimePermissions.getVersion();
+            if (version == RuntimePermissionsState.NO_VERSION) {
+                version = UPGRADE_VERSION;
+            }
+            mVersions.put(userId, version);
+
+            String fingerprint = runtimePermissions.getFingerprint();
+            mFingerprints.put(userId, fingerprint);
+            boolean defaultPermissionsGranted = Build.FINGERPRINT.equals(fingerprint);
+            mDefaultPermissionsGranted.put(userId, defaultPermissionsGranted);
+
+            for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
+                    : runtimePermissions.getPackagePermissions().entrySet()) {
+                String packageName = entry.getKey();
+                List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
+
+                PackageSetting packageSetting = mPackages.get(packageName);
+                if (packageSetting == null) {
+                    Slog.w(PackageManagerService.TAG, "Unknown package:" + packageName);
+                    continue;
+                }
+                readPermissionsStateLpr(permissions, packageSetting.getPermissionsState(), userId);
+            }
+
+            for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
+                    : runtimePermissions.getSharedUserPermissions().entrySet()) {
+                String sharedUserName = entry.getKey();
+                List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
+
+                SharedUserSetting sharedUserSetting = mSharedUsers.get(sharedUserName);
+                if (sharedUserSetting == null) {
+                    Slog.w(PackageManagerService.TAG, "Unknown shared user:" + sharedUserName);
+                    continue;
+                }
+                readPermissionsStateLpr(permissions, sharedUserSetting.getPermissionsState(),
+                        userId);
+            }
+        }
+
+        private void readPermissionsStateLpr(
+                @NonNull List<RuntimePermissionsState.PermissionState> permissions,
+                @NonNull PermissionsState permissionsState, @UserIdInt int userId) {
+            int permissionsSize = permissions.size();
+            for (int i = 0; i < permissionsSize; i++) {
+                RuntimePermissionsState.PermissionState permission = permissions.get(i);
+
+                String name = permission.getName();
+                BasePermission basePermission = mPermissions.getPermission(name);
+                if (basePermission == null) {
+                    Slog.w(PackageManagerService.TAG, "Unknown permission:" + name);
+                    continue;
+                }
+                boolean granted = permission.isGranted();
+                int flags = permission.getFlags();
+
+                if (granted) {
+                    permissionsState.grantRuntimePermission(basePermission, userId);
+                    permissionsState.updatePermissionFlags(basePermission, userId,
+                            PackageManager.MASK_PERMISSION_FLAGS_ALL, flags);
+                } else {
+                    permissionsState.updatePermissionFlags(basePermission, userId,
+                            PackageManager.MASK_PERMISSION_FLAGS_ALL, flags);
+                }
+            }
+        }
+
+        @GuardedBy("Settings.this.mLock")
+        private void readLegacyStateForUserSyncLPr(int userId) {
             File permissionsFile = getUserRuntimePermissionsFile(userId);
             if (!permissionsFile.exists()) {
                 return;
@@ -5435,19 +5491,6 @@
             }
         }
 
-        private void writePermissions(XmlSerializer serializer,
-                List<PermissionState> permissionStates) throws IOException {
-            for (PermissionState permissionState : permissionStates) {
-                serializer.startTag(null, TAG_ITEM);
-                serializer.attribute(null, ATTR_NAME,permissionState.getName());
-                serializer.attribute(null, ATTR_GRANTED,
-                        String.valueOf(permissionState.isGranted()));
-                serializer.attribute(null, ATTR_FLAGS,
-                        Integer.toHexString(permissionState.getFlags()));
-                serializer.endTag(null, TAG_ITEM);
-            }
-        }
-
         private final class MyHandler extends Handler {
             public MyHandler() {
                 super(BackgroundThread.getHandler().getLooper());
diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java
index d33c10c..9f4ca3c 100644
--- a/services/core/java/com/android/server/role/RoleUserState.java
+++ b/services/core/java/com/android/server/role/RoleUserState.java
@@ -23,6 +23,7 @@
 import android.annotation.WorkerThread;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -34,22 +35,21 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.internal.util.function.pooled.PooledLambda;
-
-import libcore.io.IoUtils;
+import com.android.role.persistence.RolesPersistence;
+import com.android.role.persistence.RolesState;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Stores the state of roles for a user.
@@ -71,6 +71,8 @@
     private static final String ATTRIBUTE_NAME = "name";
     private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash";
 
+    private final RolesPersistence mPersistence = RolesPersistence.createInstance();
+
     @UserIdInt
     private final int mUserId;
 
@@ -350,9 +352,7 @@
 
     @WorkerThread
     private void writeFile() {
-        int version;
-        String packagesHash;
-        ArrayMap<String, ArraySet<String>> roles;
+        RolesState roles;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
@@ -360,90 +360,45 @@
 
             mWriteScheduled = false;
 
-            version = mVersion;
-            packagesHash = mPackagesHash;
-            roles = snapshotRolesLocked();
+            roles = new RolesState(mVersion, mPackagesHash,
+                    (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked());
         }
 
-        AtomicFile atomicFile = new AtomicFile(getFile(mUserId), "roles-" + mUserId);
-        FileOutputStream out = null;
-        try {
-            out = atomicFile.startWrite();
-
-            XmlSerializer serializer = Xml.newSerializer();
-            serializer.setOutput(out, StandardCharsets.UTF_8.name());
-            serializer.setFeature(
-                    "http://xmlpull.org/v1/doc/features.html#indent-output", true);
-            serializer.startDocument(null, true);
-
-            serializeRoles(serializer, version, packagesHash, roles);
-
-            serializer.endDocument();
-            atomicFile.finishWrite(out);
-            Slog.i(LOG_TAG, "Wrote roles.xml successfully");
-        } catch (IllegalArgumentException | IllegalStateException | IOException e) {
-            Slog.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup", e);
-            if (out != null) {
-                atomicFile.failWrite(out);
-            }
-        } finally {
-            IoUtils.closeQuietly(out);
-        }
+        mPersistence.write(roles, UserHandle.of(mUserId));
     }
 
-    @WorkerThread
-    private void serializeRoles(@NonNull XmlSerializer serializer, int version,
-            @Nullable String packagesHash, @NonNull ArrayMap<String, ArraySet<String>> roles)
-            throws IOException {
-        serializer.startTag(null, TAG_ROLES);
-
-        serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
-
-        if (packagesHash != null) {
-            serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash);
-        }
-
-        for (int i = 0, size = roles.size(); i < size; ++i) {
-            String roleName = roles.keyAt(i);
-            ArraySet<String> roleHolders = roles.valueAt(i);
-
-            serializer.startTag(null, TAG_ROLE);
-            serializer.attribute(null, ATTRIBUTE_NAME, roleName);
-            serializeRoleHolders(serializer, roleHolders);
-            serializer.endTag(null, TAG_ROLE);
-        }
-
-        serializer.endTag(null, TAG_ROLES);
-    }
-
-    @WorkerThread
-    private void serializeRoleHolders(@NonNull XmlSerializer serializer,
-            @NonNull ArraySet<String> roleHolders) throws IOException {
-        for (int i = 0, size = roleHolders.size(); i < size; ++i) {
-            String roleHolder = roleHolders.valueAt(i);
-
-            serializer.startTag(null, TAG_HOLDER);
-            serializer.attribute(null, ATTRIBUTE_NAME, roleHolder);
-            serializer.endTag(null, TAG_HOLDER);
-        }
-    }
-
-    /**
-     * Read the state from file.
-     */
     private void readFile() {
         synchronized (mLock) {
-            File file = getFile(mUserId);
-            try (FileInputStream in = new AtomicFile(file).openRead()) {
-                XmlPullParser parser = Xml.newPullParser();
-                parser.setInput(in, null);
-                parseXmlLocked(parser);
-                Slog.i(LOG_TAG, "Read roles.xml successfully");
-            } catch (FileNotFoundException e) {
-                Slog.i(LOG_TAG, "roles.xml not found");
-            } catch (XmlPullParserException | IOException e) {
-                throw new IllegalStateException("Failed to parse roles.xml: " + file, e);
+            RolesState roles = mPersistence.read(UserHandle.of(mUserId));
+            if (roles == null) {
+                readLegacyFileLocked();
+                scheduleWriteFileLocked();
+                return;
             }
+
+            mVersion = roles.getVersion();
+            mPackagesHash = roles.getPackagesHash();
+
+            mRoles.clear();
+            for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) {
+                String roleName = entry.getKey();
+                ArraySet<String> roleHolders = new ArraySet<>(entry.getValue());
+                mRoles.put(roleName, roleHolders);
+            }
+        }
+    }
+
+    private void readLegacyFileLocked() {
+        File file = getFile(mUserId);
+        try (FileInputStream in = new AtomicFile(file).openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(in, null);
+            parseXmlLocked(parser);
+            Slog.i(LOG_TAG, "Read roles.xml successfully");
+        } catch (FileNotFoundException e) {
+            Slog.i(LOG_TAG, "roles.xml not found");
+        } catch (XmlPullParserException | IOException e) {
+            throw new IllegalStateException("Failed to parse roles.xml: " + file, e);
         }
     }
 
@@ -590,7 +545,7 @@
                 throw new IllegalStateException("This RoleUserState has already been destroyed");
             }
             mWriteHandler.removeCallbacksAndMessages(null);
-            getFile(mUserId).delete();
+            mPersistence.delete(UserHandle.of(mUserId));
             mDestroyed = true;
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java
index 6b0f557..34ade81 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java
@@ -103,7 +103,7 @@
                 () -> impl.setSchema(
                         /*callingUid=*/Integer.MAX_VALUE,
                         SchemaProto.getDefaultInstance(),
-                        /*force=*/false));
+                        /*forceOverride=*/false));
         assertThat(e).hasMessageThat().contains("Failed to look up package name");
     }
 }
diff --git a/telephony/java/android/telephony/PinResult.java b/telephony/java/android/telephony/PinResult.java
new file mode 100644
index 0000000..683e853
--- /dev/null
+++ b/telephony/java/android/telephony/PinResult.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.telephony.PhoneConstants;
+
+import java.util.Objects;
+
+/**
+ * Holds the result from a pin attempt.
+ *
+ * @hide
+ */
+@SystemApi
+public final class PinResult implements Parcelable {
+    /** @hide */
+    @IntDef({
+            PIN_RESULT_TYPE_SUCCESS,
+            PIN_RESULT_TYPE_INCORRECT,
+            PIN_RESULT_TYPE_FAILURE,
+    })
+    public @interface PinResultType {}
+
+    /**
+     * Indicates that the pin attempt was a success.
+     */
+    public static final int PIN_RESULT_TYPE_SUCCESS = PhoneConstants.PIN_RESULT_SUCCESS;
+
+    /**
+     * Indicates that the pin attempt was incorrect.
+     */
+    public static final int PIN_RESULT_TYPE_INCORRECT = PhoneConstants.PIN_PASSWORD_INCORRECT;
+
+    /**
+     * Indicates that the pin attempt was a failure.
+     */
+    public static final int PIN_RESULT_TYPE_FAILURE = PhoneConstants.PIN_GENERAL_FAILURE;
+
+    private static final PinResult sFailedResult =
+            new PinResult(PinResult.PIN_RESULT_TYPE_FAILURE, -1);
+
+    private final @PinResultType int mType;
+
+    private final int mAttemptsRemaining;
+
+    /**
+     * Returns either success, incorrect or failure.
+     *
+     * @see: #PIN_RESULT_TYPE_SUCCESS
+     * @see: #PIN_RESULT_TYPE_INCORRECT
+     * @see: #PIN_RESULT_TYPE_FAILURE
+     * @return The result type of the pin attempt.
+     */
+    public @PinResultType int getType() {
+        return mType;
+    }
+
+    /**
+     * The number of pin attempts remaining.
+     *
+     * @return Number of attempts remaining.
+     */
+    public int getAttemptsRemaining() {
+        return mAttemptsRemaining;
+    }
+
+    @NonNull
+    public static PinResult getDefaultFailedResult() {
+        return sFailedResult;
+    }
+
+    /**
+     * PinResult constructor
+     *
+     * @param type The type of pin result.
+     * @see: #PIN_RESULT_TYPE_SUCCESS
+     * @see: #PIN_RESULT_TYPE_INCORRECT
+     * @see: #PIN_RESULT_TYPE_FAILURE
+     * @param attemptsRemaining Number of pin attempts remaining.
+     */
+    public PinResult(@PinResultType int type, int attemptsRemaining) {
+        mType = type;
+        mAttemptsRemaining = attemptsRemaining;
+    }
+
+    /**
+     * Construct a PinResult object from the given parcel.
+     *
+     * @hide
+     */
+    private PinResult(Parcel in) {
+        mType = in.readInt();
+        mAttemptsRemaining = in.readInt();
+    }
+
+    /**
+     * String representation of the Pin Result.
+     */
+    @NonNull
+    @Override
+    public String toString() {
+        return "type: " + getType() + ", attempts remaining: " + getAttemptsRemaining();
+    }
+
+    /**
+     * Required to be Parcelable
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Required to be Parcelable
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mType);
+        out.writeInt(mAttemptsRemaining);
+    }
+
+    /** Required to be Parcelable */
+    public static final @NonNull Parcelable.Creator<PinResult> CREATOR = new Creator<PinResult>() {
+        public PinResult createFromParcel(Parcel in) {
+            return new PinResult(in);
+        }
+        public PinResult[] newArray(int size) {
+            return new PinResult[size];
+        }
+    };
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAttemptsRemaining, mType);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        PinResult other = (PinResult) obj;
+        return (mType == other.mType
+                && mAttemptsRemaining == other.mAttemptsRemaining);
+    }
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4aedf67..4c3cba5 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -8547,9 +8547,13 @@
         return false;
     }
 
-    /** @hide */
+    /**
+     * @deprecated use {@link #supplyPinReportPinResult(String pin)} instead.
+     *
+     * @hide */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @Deprecated
     public int[] supplyPinReportResult(String pin) {
         try {
             ITelephony telephony = getITelephony();
@@ -8561,9 +8565,13 @@
         return new int[0];
     }
 
-    /** @hide */
+    /**
+     * @deprecated use {@link #supplyPukReportPinResult(String puk, String pin)} instead.
+     *
+     * @hide */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @Deprecated
     public int[] supplyPukReportResult(String puk, String pin) {
         try {
             ITelephony telephony = getITelephony();
@@ -8576,6 +8584,55 @@
     }
 
     /**
+     * Used when the user attempts to enter their pin.
+     *
+     * @param pin The user entered pin.
+     * @return The result of the pin.
+     *
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public PinResult supplyPinReportPinResult(@NonNull String pin) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                int[] result = telephony.supplyPinReportResultForSubscriber(getSubId(), pin);
+                return new PinResult(result[0], result[1]);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#supplyPinReportResultForSubscriber", e);
+        }
+        return null;
+    }
+
+    /**
+     * Used when the user attempts to enter the puk or their pin.
+     *
+     * @param puk The product unblocking key.
+     * @param pin The user entered pin.
+     * @return The result of the pin.
+     *
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public PinResult supplyPukReportPinResult(@NonNull String puk, @NonNull String pin) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                int[] result = telephony.supplyPukReportResultForSubscriber(getSubId(), puk, pin);
+                return new PinResult(result[0], result[1]);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#]", e);
+        }
+        return null;
+    }
+
+    /**
      * Used to notify callers of
      * {@link TelephonyManager#sendUssdRequest(String, UssdResponseCallback, Handler)} when the
      * network either successfully executes a USSD request, or if there was a failure while
diff --git a/wifi/java/android/net/wifi/ILocalOnlyHotspotCallback.aidl b/wifi/java/android/net/wifi/ILocalOnlyHotspotCallback.aidl
index b83b594..b567f29 100644
--- a/wifi/java/android/net/wifi/ILocalOnlyHotspotCallback.aidl
+++ b/wifi/java/android/net/wifi/ILocalOnlyHotspotCallback.aidl
@@ -16,7 +16,7 @@
 
 package android.net.wifi;
 
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
 
 /**
  * Communicates LOHS status back to the application process.
@@ -24,7 +24,7 @@
  * @hide
  */
 oneway interface ILocalOnlyHotspotCallback {
-    void onHotspotStarted(in WifiConfiguration config);
+    void onHotspotStarted(in SoftApConfiguration config);
     void onHotspotStopped();
     void onHotspotFailed(int reason);
 }
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index a77d30a..49fb5a3 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -36,7 +36,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.Executor;
 
 /**
  * Configuration for a soft access point (a.k.a. Soft AP, SAP, Hotspot).
@@ -45,22 +44,21 @@
  * framework how it should configure a hotspot.
  *
  * System apps can use this to configure a tethered hotspot using
- * {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} and
- * {@link WifiManager#setSoftApConfiguration(SoftApConfiguration)}
+ * {@code WifiManager#startTetheredHotspot(SoftApConfiguration)} and
+ * {@code WifiManager#setSoftApConfiguration(SoftApConfiguration)}
  * or local-only hotspot using
- * {@link WifiManager#startLocalOnlyHotspot(SoftApConfiguration, Executor,
+ * {@code WifiManager#startLocalOnlyHotspot(SoftApConfiguration, Executor,
  * WifiManager.LocalOnlyHotspotCallback)}.
  *
  * Instances of this class are immutable; use {@link SoftApConfiguration.Builder} and its methods to
  * create a new instance.
  *
- * @hide
  */
-@SystemApi
 public final class SoftApConfiguration implements Parcelable {
 
     @VisibleForTesting
     static final int PSK_MIN_LEN = 8;
+
     @VisibleForTesting
     static final int PSK_MAX_LEN = 63;
 
@@ -207,22 +205,24 @@
     private final int mShutdownTimeoutMillis;
 
     /**
-     * Security types we support.
+     * THe definition of security type OPEN.
      */
-    /** @hide */
-    @SystemApi
     public static final int SECURITY_TYPE_OPEN = 0;
 
-    /** @hide */
-    @SystemApi
+
+    /**
+     * The definition of security type WPA2-PSK.
+     */
     public static final int SECURITY_TYPE_WPA2_PSK = 1;
 
-    /** @hide */
-    @SystemApi
+    /**
+     * The definition of security type WPA3-SAE Transition mode.
+     */
     public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2;
 
-    /** @hide */
-    @SystemApi
+    /**
+     * The definition of security type WPA3-SAE.
+     */
     public static final int SECURITY_TYPE_WPA3_SAE = 3;
 
     /** @hide */
@@ -346,7 +346,7 @@
 
     /**
      * Return String set to be the SSID for the AP.
-     * {@link #setSsid(String)}.
+     * {@link Builder#setSsid(String)}.
      */
     @Nullable
     public String getSsid() {
@@ -364,7 +364,7 @@
 
     /**
      * Returns String set to be passphrase for current AP.
-     * {@link #setPassphrase(String, @SecurityType int)}.
+     * {@link Builder#setPassphrase(String, int)}.
      */
     @Nullable
     public String getPassphrase() {
@@ -383,7 +383,10 @@
     /**
      * Returns {@link BandType} set to be the band for the AP.
      * {@link Builder#setBand(@BandType int)}.
+     *
+     * @hide
      */
+    @SystemApi
     public @BandType int getBand() {
         return mBand;
     }
@@ -391,7 +394,10 @@
     /**
      * Returns Integer set to be the channel for the AP.
      * {@link Builder#setChannel(int)}.
+     *
+     * @hide
      */
+    @SystemApi
     public int getChannel() {
         return mChannel;
     }
@@ -408,7 +414,10 @@
     /**
      * Returns the maximum number of clients that can associate to the AP.
      * {@link Builder#setMaxNumberOfClients(int)}.
+     *
+     * @hide
      */
+    @SystemApi
     public int getMaxNumberOfClients() {
         return mMaxNumberOfClients;
     }
@@ -417,7 +426,10 @@
      * Returns the shutdown timeout in milliseconds.
      * The Soft AP will shutdown when there are no devices associated to it for
      * the timeout duration. See {@link Builder#setShutdownTimeoutMillis(int)}.
+     *
+     * @hide
      */
+    @SystemApi
     public int getShutdownTimeoutMillis() {
         return mShutdownTimeoutMillis;
     }
@@ -426,7 +438,10 @@
      * Returns a flag indicating whether clients need to be pre-approved by the user.
      * (true: authorization required) or not (false: not required).
      * {@link Builder#enableClientControlByUser(Boolean)}.
+     *
+     * @hide
      */
+    @SystemApi
     public boolean isClientControlByUserEnabled() {
         return mClientControlByUser;
     }
@@ -435,8 +450,11 @@
      * Returns List of clients which aren't allowed to associate to the AP.
      *
      * Clients are configured using {@link Builder#setClientList(List, List)}
+     *
+     * @hide
      */
     @NonNull
+    @SystemApi
     public List<MacAddress> getBlockedClientList() {
         return mBlockedClientList;
     }
@@ -444,8 +462,11 @@
     /**
      * List of clients which are allowed to associate to the AP.
      * Clients are configured using {@link Builder#setClientList(List, List)}
+     *
+     * @hide
      */
     @NonNull
+    @SystemApi
     public List<MacAddress> getAllowedClientList() {
         return mAllowedClientList;
     }
@@ -456,7 +477,10 @@
      *
      * All fields are optional. By default, SSID and BSSID are automatically chosen by the
      * framework, and an open network is created.
+     *
+     * @hide
      */
+    @SystemApi
     public static final class Builder {
         private String mSsid;
         private MacAddress mBssid;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 64d4eaf..1b74cba 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1948,6 +1948,15 @@
      * @param config The Passpoint configuration to be added
      * @throws IllegalArgumentException if configuration is invalid or Passpoint is not enabled on
      *                                  the device.
+     *
+     * Deprecated for general app usage - except DO/PO apps.
+     * See {@link WifiNetworkSuggestion.Builder#setPasspointConfig(PasspointConfiguration)} to
+     * create a passpoint suggestion.
+     * See {@link #addNetworkSuggestions(List)}, {@link #removeNetworkSuggestions(List)} for new
+     * API to add Wi-Fi networks for consideration when auto-connecting to wifi.
+     * <b>Compatibility Note:</b> For applications targeting
+     * {@link android.os.Build.VERSION_CODES#R} or above, except for system of DO/PO apps, this API
+     * will throw {@link IllegalArgumentException}
      */
     public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
         try {
@@ -2971,7 +2980,7 @@
      * Each application can make a single active call to this method. The {@link
      * LocalOnlyHotspotCallback#onStarted(LocalOnlyHotspotReservation)} callback supplies the
      * requestor with a {@link LocalOnlyHotspotReservation} that contains a
-     * {@link WifiConfiguration} with the SSID, security type and credentials needed to connect
+     * {@link SoftApConfiguration} with the SSID, security type and credentials needed to connect
      * to the hotspot.  Communicating this information is up to the application.
      * <p>
      * If the LocalOnlyHotspot cannot be created, the {@link LocalOnlyHotspotCallback#onFailed(int)}
@@ -3136,7 +3145,7 @@
      * Allow callers (Settings UI) to watch LocalOnlyHotspot state changes.  Callers will
      * receive a {@link LocalOnlyHotspotSubscription} object as a parameter of the
      * {@link LocalOnlyHotspotObserver#onRegistered(LocalOnlyHotspotSubscription)}. The registered
-     * callers will receive the {@link LocalOnlyHotspotObserver#onStarted(WifiConfiguration)} and
+     * callers will receive the {@link LocalOnlyHotspotObserver#onStarted(SoftApConfiguration)} and
      * {@link LocalOnlyHotspotObserver#onStopped()} callbacks.
      * <p>
      * Applications should have the
@@ -3711,13 +3720,13 @@
     }
 
     /**
-     * LocalOnlyHotspotReservation that contains the {@link WifiConfiguration} for the active
+     * LocalOnlyHotspotReservation that contains the {@link SoftApConfiguration} for the active
      * LocalOnlyHotspot request.
      * <p>
      * Applications requesting LocalOnlyHotspot for sharing will receive an instance of the
      * LocalOnlyHotspotReservation in the
      * {@link LocalOnlyHotspotCallback#onStarted(LocalOnlyHotspotReservation)} call.  This
-     * reservation contains the relevant {@link WifiConfiguration}.
+     * reservation contains the relevant {@link SoftApConfiguration}.
      * When an application is done with the LocalOnlyHotspot, they should call {@link
      * LocalOnlyHotspotReservation#close()}.  Once this happens, the application will not receive
      * any further callbacks. If the LocalOnlyHotspot is stopped due to a
@@ -3727,20 +3736,71 @@
     public class LocalOnlyHotspotReservation implements AutoCloseable {
 
         private final CloseGuard mCloseGuard = new CloseGuard();
-        private final WifiConfiguration mConfig;
+        private final SoftApConfiguration mConfig;
         private boolean mClosed = false;
 
         /** @hide */
         @VisibleForTesting
-        public LocalOnlyHotspotReservation(WifiConfiguration config) {
+        public LocalOnlyHotspotReservation(SoftApConfiguration config) {
             mConfig = config;
             mCloseGuard.open("close");
         }
 
+        /**
+         * Returns the {@link WifiConfiguration} of the current Local Only Hotspot (LOHS).
+         * May be null if hotspot enabled and security type is not
+         * {@code WifiConfiguration.KeyMgmt.None} or {@code WifiConfiguration.KeyMgmt.WPA2_PSK}.
+         *
+         * @deprecated Use {@code WifiManager#getSoftApConfiguration()} to get the
+         * LOHS configuration.
+         */
+        @Deprecated
+        @Nullable
         public WifiConfiguration getWifiConfiguration() {
+            return convertToWifiConfiguration(mConfig);
+        }
+
+        /**
+         * Returns the {@link SoftApConfiguration} of the current Local Only Hotspot (LOHS).
+         */
+        @NonNull
+        public SoftApConfiguration getSoftApConfiguration() {
             return mConfig;
         }
 
+        /**
+         * Convert to WifiConfiguration from SoftApConfuration.
+         *
+         * Copy to the filed which is public and used by SoftAp.
+         */
+        private WifiConfiguration convertToWifiConfiguration(SoftApConfiguration softApConfig) {
+            if (softApConfig == null) return null;
+
+            WifiConfiguration wifiConfig = new WifiConfiguration();
+            wifiConfig.networkId = WifiConfiguration.LOCAL_ONLY_NETWORK_ID;
+            wifiConfig.SSID = softApConfig.getSsid();
+            if (softApConfig.getBssid() != null) {
+                wifiConfig.BSSID = softApConfig.getBssid().toString();
+            }
+            wifiConfig.preSharedKey = softApConfig.getPassphrase();
+            wifiConfig.hiddenSSID = softApConfig.isHiddenSsid();
+            int authType = softApConfig.getSecurityType();
+            switch (authType) {
+                case SoftApConfiguration.SECURITY_TYPE_OPEN:
+                    authType = WifiConfiguration.KeyMgmt.NONE;
+                    wifiConfig.allowedKeyManagement.set(authType);
+                    break;
+                case SoftApConfiguration.SECURITY_TYPE_WPA2_PSK:
+                    authType = WifiConfiguration.KeyMgmt.WPA2_PSK;
+                    wifiConfig.allowedKeyManagement.set(authType);
+                    break;
+                default:
+                    wifiConfig = null;
+                    break;
+            }
+            return wifiConfig;
+        }
+
         @Override
         public void close() {
             try {
@@ -3835,7 +3895,7 @@
         }
 
         @Override
-        public void onHotspotStarted(WifiConfiguration config) {
+        public void onHotspotStarted(SoftApConfiguration config) {
             WifiManager manager = mWifiManager.get();
             if (manager == null) return;
 
@@ -3927,7 +3987,7 @@
         /**
          * LocalOnlyHotspot started with the supplied config.
          */
-        public void onStarted(WifiConfiguration config) {};
+        public void onStarted(SoftApConfiguration config) {};
 
         /**
          * LocalOnlyHotspot stopped.
@@ -3967,7 +4027,7 @@
         }
 
         @Override
-        public void onHotspotStarted(WifiConfiguration config) {
+        public void onHotspotStarted(SoftApConfiguration config) {
             WifiManager manager = mWifiManager.get();
             if (manager == null) return;
 
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 2fba5a3..7ecad9e 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -635,14 +635,12 @@
                     }
                     mIsSharedWithUser = false;
                 }
-
-                if (!mIsSharedWithUser && !mIsInitialAutoJoinEnabled) {
-                    throw new IllegalStateException("Should have not a network with both "
-                            + "setIsUserAllowedToManuallyConnect and "
-                            + "setIsAutoJoinEnabled set to false");
-                }
             }
-
+            if (!mIsSharedWithUser && !mIsInitialAutoJoinEnabled) {
+                throw new IllegalStateException("Should have not a network with both "
+                        + "setCredentialSharedWithUser and "
+                        + "setIsAutoJoinEnabled set to false");
+            }
             return new WifiNetworkSuggestion(
                     wifiConfiguration,
                     mPasspointConfiguration,
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 1ee5374..738c633 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -106,6 +106,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -148,6 +149,50 @@
     private ScanResultsCallback mScanResultsCallback;
     private WifiActivityEnergyInfo mWifiActivityEnergyInfo;
 
+    /**
+     * Util function to check public field which used for softap  in WifiConfiguration
+     * same as the value in SoftApConfiguration.
+     *
+     */
+    private boolean compareWifiAndSoftApConfiguration(
+            SoftApConfiguration softApConfig, WifiConfiguration wifiConfig) {
+        if (!Objects.equals(wifiConfig.SSID, softApConfig.getSsid())) {
+            return false;
+        }
+        if (!Objects.equals(wifiConfig.BSSID, softApConfig.getBssid())) {
+            return false;
+        }
+        if (!Objects.equals(wifiConfig.preSharedKey, softApConfig.getPassphrase())) {
+            return false;
+        }
+
+        if (wifiConfig.hiddenSSID != softApConfig.isHiddenSsid()) {
+            return false;
+        }
+        switch (softApConfig.getSecurityType()) {
+            case SoftApConfiguration.SECURITY_TYPE_OPEN:
+                if (wifiConfig.getAuthType() != WifiConfiguration.KeyMgmt.NONE) {
+                    return false;
+                }
+                break;
+            case SoftApConfiguration.SECURITY_TYPE_WPA2_PSK:
+                if (wifiConfig.getAuthType() != WifiConfiguration.KeyMgmt.WPA2_PSK) {
+                    return false;
+                }
+                break;
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    private SoftApConfiguration generatorTestSoftApConfig() {
+        return new SoftApConfiguration.Builder()
+                .setSsid("TestSSID")
+                .setPassphrase("TestPassphrase", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .build();
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -212,12 +257,12 @@
      */
     @Test
     public void testStartTetheredHotspotCallsServiceWithSoftApConfig() throws Exception {
-        SoftApConfiguration mSoftApConfig = new SoftApConfiguration.Builder().build();
-        when(mWifiService.startTetheredHotspot(eq(mSoftApConfig))).thenReturn(true);
-        assertTrue(mWifiManager.startTetheredHotspot(mSoftApConfig));
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
+        when(mWifiService.startTetheredHotspot(eq(softApConfig))).thenReturn(true);
+        assertTrue(mWifiManager.startTetheredHotspot(softApConfig));
 
-        when(mWifiService.startTetheredHotspot(eq(mSoftApConfig))).thenReturn(false);
-        assertFalse(mWifiManager.startTetheredHotspot(mSoftApConfig));
+        when(mWifiService.startTetheredHotspot(eq(softApConfig))).thenReturn(false);
+        assertFalse(mWifiManager.startTetheredHotspot(softApConfig));
     }
 
     /**
@@ -239,14 +284,18 @@
      */
     @Test
     public void testCreationAndCloseOfLocalOnlyHotspotReservation() throws Exception {
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback();
         when(mWifiService.startLocalOnlyHotspot(any(ILocalOnlyHotspotCallback.class), anyString(),
                 nullable(String.class), eq(null))).thenReturn(REQUEST_REGISTERED);
         mWifiManager.startLocalOnlyHotspot(callback, mHandler);
 
-        callback.onStarted(mWifiManager.new LocalOnlyHotspotReservation(mApConfig));
+        callback.onStarted(mWifiManager.new LocalOnlyHotspotReservation(softApConfig));
 
-        assertEquals(mApConfig, callback.mRes.getWifiConfiguration());
+        assertEquals(softApConfig, callback.mRes.getSoftApConfiguration());
+        WifiConfiguration wifiConfig = callback.mRes.getWifiConfiguration();
+        assertTrue(compareWifiAndSoftApConfiguration(softApConfig, wifiConfig));
+
         callback.mRes.close();
         verify(mWifiService).stopLocalOnlyHotspot();
     }
@@ -257,15 +306,18 @@
     @Test
     public void testLocalOnlyHotspotReservationCallsStopProperlyInTryWithResources()
             throws Exception {
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback();
         when(mWifiService.startLocalOnlyHotspot(any(ILocalOnlyHotspotCallback.class), anyString(),
                 nullable(String.class), eq(null))).thenReturn(REQUEST_REGISTERED);
         mWifiManager.startLocalOnlyHotspot(callback, mHandler);
 
-        callback.onStarted(mWifiManager.new LocalOnlyHotspotReservation(mApConfig));
+        callback.onStarted(mWifiManager.new LocalOnlyHotspotReservation(softApConfig));
 
         try (WifiManager.LocalOnlyHotspotReservation res = callback.mRes) {
-            assertEquals(mApConfig, res.getWifiConfiguration());
+            assertEquals(softApConfig, res.getSoftApConfiguration());
+            WifiConfiguration wifiConfig = callback.mRes.getWifiConfiguration();
+            assertTrue(compareWifiAndSoftApConfiguration(softApConfig, wifiConfig));
         }
 
         verify(mWifiService).stopLocalOnlyHotspot();
@@ -315,6 +367,7 @@
      */
     @Test
     public void testLocalOnlyHotspotCallback() {
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback();
         assertFalse(callback.mOnStartedCalled);
         assertFalse(callback.mOnStoppedCalled);
@@ -323,7 +376,7 @@
 
         // test onStarted
         WifiManager.LocalOnlyHotspotReservation res =
-                mWifiManager.new LocalOnlyHotspotReservation(mApConfig);
+                mWifiManager.new LocalOnlyHotspotReservation(softApConfig);
         callback.onStarted(res);
         assertEquals(res, callback.mRes);
         assertTrue(callback.mOnStartedCalled);
@@ -349,7 +402,7 @@
         public boolean mOnRegistered = false;
         public boolean mOnStartedCalled = false;
         public boolean mOnStoppedCalled = false;
-        public WifiConfiguration mConfig = null;
+        public SoftApConfiguration mConfig = null;
         public LocalOnlyHotspotSubscription mSub = null;
         public long mCallingThreadId = -1;
 
@@ -361,7 +414,7 @@
         }
 
         @Override
-        public void onStarted(WifiConfiguration config) {
+        public void onStarted(SoftApConfiguration config) {
             mOnStartedCalled = true;
             mConfig = config;
             mCallingThreadId = Thread.currentThread().getId();
@@ -380,6 +433,7 @@
     @Test
     public void testLocalOnlyHotspotObserver() {
         TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver();
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
         assertFalse(observer.mOnRegistered);
         assertFalse(observer.mOnStartedCalled);
         assertFalse(observer.mOnStoppedCalled);
@@ -395,18 +449,18 @@
         assertEquals(null, observer.mConfig);
         assertEquals(sub, observer.mSub);
 
-        observer.onStarted(mApConfig);
+        observer.onStarted(softApConfig);
         assertTrue(observer.mOnRegistered);
         assertTrue(observer.mOnStartedCalled);
         assertFalse(observer.mOnStoppedCalled);
-        assertEquals(mApConfig, observer.mConfig);
+        assertEquals(softApConfig, observer.mConfig);
         assertEquals(sub, observer.mSub);
 
         observer.onStopped();
         assertTrue(observer.mOnRegistered);
         assertTrue(observer.mOnStartedCalled);
         assertTrue(observer.mOnStoppedCalled);
-        assertEquals(mApConfig, observer.mConfig);
+        assertEquals(softApConfig, observer.mConfig);
         assertEquals(sub, observer.mSub);
     }
 
@@ -488,6 +542,7 @@
      */
     @Test
     public void testOnStartedIsCalledWithReservation() throws Exception {
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback();
         TestLooper callbackLooper = new TestLooper();
         Handler callbackHandler = new Handler(callbackLooper.getLooper());
@@ -501,11 +556,44 @@
         assertFalse(callback.mOnStartedCalled);
         assertEquals(null, callback.mRes);
         // now trigger the callback
-        internalCallback.getValue().onHotspotStarted(mApConfig);
+        internalCallback.getValue().onHotspotStarted(softApConfig);
         mLooper.dispatchAll();
         callbackLooper.dispatchAll();
         assertTrue(callback.mOnStartedCalled);
-        assertEquals(mApConfig, callback.mRes.getWifiConfiguration());
+        assertEquals(softApConfig, callback.mRes.getSoftApConfiguration());
+        WifiConfiguration wifiConfig = callback.mRes.getWifiConfiguration();
+        assertTrue(compareWifiAndSoftApConfiguration(softApConfig, wifiConfig));
+    }
+
+    /**
+     * Verify the LOHS onStarted callback is triggered when WifiManager receives a HOTSPOT_STARTED
+     * message from WifiServiceImpl when softap enabled with SAE security type.
+     */
+    @Test
+    public void testOnStartedIsCalledWithReservationAndSaeSoftApConfig() throws Exception {
+        SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
+                .setSsid("TestSSID")
+                .setPassphrase("TestPassphrase", SoftApConfiguration.SECURITY_TYPE_WPA3_SAE)
+                .build();
+        TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback();
+        TestLooper callbackLooper = new TestLooper();
+        Handler callbackHandler = new Handler(callbackLooper.getLooper());
+        ArgumentCaptor<ILocalOnlyHotspotCallback> internalCallback =
+                ArgumentCaptor.forClass(ILocalOnlyHotspotCallback.class);
+        when(mWifiService.startLocalOnlyHotspot(internalCallback.capture(), anyString(),
+                nullable(String.class), eq(null))).thenReturn(REQUEST_REGISTERED);
+        mWifiManager.startLocalOnlyHotspot(callback, callbackHandler);
+        callbackLooper.dispatchAll();
+        mLooper.dispatchAll();
+        assertFalse(callback.mOnStartedCalled);
+        assertEquals(null, callback.mRes);
+        // now trigger the callback
+        internalCallback.getValue().onHotspotStarted(softApConfig);
+        mLooper.dispatchAll();
+        callbackLooper.dispatchAll();
+        assertTrue(callback.mOnStartedCalled);
+        assertEquals(softApConfig, callback.mRes.getSoftApConfiguration());
+        assertEquals(null, callback.mRes.getWifiConfiguration());
     }
 
     /**
@@ -1037,6 +1125,7 @@
      */
     @Test
     public void testObserverOnStartedIsCalledWithWifiConfig() throws Exception {
+        SoftApConfiguration softApConfig = generatorTestSoftApConfig();
         TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver();
         TestLooper observerLooper = new TestLooper();
         Handler observerHandler = new Handler(observerLooper.getLooper());
@@ -1048,11 +1137,11 @@
         mLooper.dispatchAll();
         assertFalse(observer.mOnStartedCalled);
         // now trigger the callback
-        internalCallback.getValue().onHotspotStarted(mApConfig);
+        internalCallback.getValue().onHotspotStarted(softApConfig);
         mLooper.dispatchAll();
         observerLooper.dispatchAll();
         assertTrue(observer.mOnStartedCalled);
-        assertEquals(mApConfig, observer.mConfig);
+        assertEquals(softApConfig, observer.mConfig);
     }
 
     /**
@@ -1248,7 +1337,7 @@
      */
     @Test
     public void testSetSoftApConfigurationSuccessReturnsTrue() throws Exception {
-        SoftApConfiguration apConfig = new SoftApConfiguration.Builder().build();
+        SoftApConfiguration apConfig = generatorTestSoftApConfig();
 
         when(mWifiService.setSoftApConfiguration(eq(apConfig), eq(TEST_PACKAGE_NAME)))
                 .thenReturn(true);
@@ -1260,7 +1349,7 @@
      */
     @Test
     public void testSetSoftApConfigurationFailureReturnsFalse() throws Exception {
-        SoftApConfiguration apConfig = new SoftApConfiguration.Builder().build();
+        SoftApConfiguration apConfig = generatorTestSoftApConfig();
 
         when(mWifiService.setSoftApConfiguration(eq(apConfig), eq(TEST_PACKAGE_NAME)))
                 .thenReturn(false);
@@ -1275,7 +1364,7 @@
         doThrow(new SecurityException()).when(mWifiService).setSoftApConfiguration(any(), any());
 
         try {
-            mWifiManager.setSoftApConfiguration(new SoftApConfiguration.Builder().build());
+            mWifiManager.setSoftApConfiguration(generatorTestSoftApConfig());
             fail("setWifiApConfiguration should rethrow Exceptions from WifiService");
         } catch (SecurityException e) { }
     }
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index cb1b774..e778b9a 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -758,4 +758,20 @@
                 .setIsInitialAutoJoinEnabled(false)
                 .build();
     }
+
+    /**
+     * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+     * when set both {@link WifiNetworkSuggestion.Builder#setIsInitialAutoJoinEnabled(boolean)}
+     * and {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} (boolean)}
+     * to false on a passpoint suggestion.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testSetIsAutoJoinDisabledWithSecureNetworkNotSharedWithUserForPasspoint() {
+        PasspointConfiguration passpointConfiguration = PasspointTestUtils.createConfig();
+        new WifiNetworkSuggestion.Builder()
+                .setPasspointConfig(passpointConfiguration)
+                .setCredentialSharedWithUser(false)
+                .setIsInitialAutoJoinEnabled(false)
+                .build();
+    }
 }