Merge "Add a UWB subsystem uid" into sc-dev
diff --git a/Android.bp b/Android.bp
index d75239a..7c9cdcf 100644
--- a/Android.bp
+++ b/Android.bp
@@ -483,6 +483,19 @@
     "--api-lint-ignore-prefix junit. " +
     "--api-lint-ignore-prefix org. "
 
+packages_to_document = [
+    "android",
+    "dalvik",
+    "java",
+    "javax",
+    "junit",
+    "org.apache.http",
+    "org.json",
+    "org.w3c.dom",
+    "org.xml.sax",
+    "org.xmlpull",
+]
+
 filegroup {
     name: "android-non-updatable-stub-sources",
     srcs: [
@@ -535,7 +548,7 @@
         "android.hardware.usb.gadget-V1.0-java",
         "android.hardware.vibrator-V1.3-java",
         "framework-protos",
-        "stable.core.platform.api.stubs",
+        "art.module.public.api",
         // There are a few classes from modules used by the core that
         // need to be resolved by metalava. We use a prebuilt stub of the
         // full sdk to ensure we can resolve them. If a new class gets added,
@@ -544,6 +557,7 @@
         // NOTE: The below can be removed once the prebuilt stub contains IKE.
         "sdk_system_current_android.net.ipsec.ike",
     ],
+    filter_packages: packages_to_document,
     high_mem: true, // Lots of sources => high memory use, see b/170701554
     installable: false,
     annotations_enabled: true,
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 0f218b5..0aed5d9 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -93,13 +93,14 @@
         ":updatable-media-srcs",
 
         // No longer part of the stubs, but are included in the docs.
-        "test-base/src/**/*.java",
-        "test-mock/src/**/*.java",
-        "test-runner/src/**/*.java",
+        ":android-test-base-sources",
+        ":android-test-mock-sources",
+        ":android-test-runner-sources",
     ],
     libs: framework_docs_only_libs,
     create_doc_stubs: true,
     annotations_enabled: true,
+    filter_packages: packages_to_document,
     api_levels_annotations_enabled: true,
     api_levels_annotations_dirs: [
         "sdk-dir",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 7b8a687..ed24d43 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -27,24 +27,10 @@
 // Common metalava configs
 /////////////////////////////////////////////////////////////////////
 
-packages_to_document = [
-    "android",
-    "dalvik",
-    "java",
-    "javax",
-    "junit",
-    "org.apache.http",
-    "org.json",
-    "org.w3c.dom",
-    "org.xml.sax",
-    "org.xmlpull",
-]
-
 stubs_defaults {
     name: "metalava-non-updatable-api-stubs-default",
     defaults: ["android-non-updatable-stubs-defaults"],
     api_levels_annotations_enabled: false,
-    filter_packages: packages_to_document,
     defaults_visibility: ["//visibility:private"],
 }
 
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index 4dff436..5a2f702 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -290,14 +290,12 @@
     method @NonNull public String getDatabaseName();
     method @NonNull public android.app.appsearch.GenericDocument getGenericDocument();
     method @NonNull public java.util.List<android.app.appsearch.SearchResult.MatchInfo> getMatchInfos();
-    method @Deprecated @NonNull public java.util.List<android.app.appsearch.SearchResult.MatchInfo> getMatches();
     method @NonNull public String getPackageName();
     method public double getRankingSignal();
   }
 
   public static final class SearchResult.Builder {
     ctor public SearchResult.Builder(@NonNull String, @NonNull String);
-    method @Deprecated @NonNull public android.app.appsearch.SearchResult.Builder addMatch(@NonNull android.app.appsearch.SearchResult.MatchInfo);
     method @NonNull public android.app.appsearch.SearchResult.Builder addMatchInfo(@NonNull android.app.appsearch.SearchResult.MatchInfo);
     method @NonNull public android.app.appsearch.SearchResult build();
     method @NonNull public android.app.appsearch.SearchResult.Builder setGenericDocument(@NonNull android.app.appsearch.GenericDocument);
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.aidl b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.aidl
deleted file mode 100644
index 4686de8..0000000
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright 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.app.appsearch;
-
-/** {@hide} */
-parcelable AppSearchBatchResult;
\ No newline at end of file
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index c4dd1a0..bc3c79f 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -18,6 +18,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.SystemService;
+import android.app.appsearch.aidl.IAppSearchManager;
 import android.content.Context;
 
 import com.android.internal.util.Preconditions;
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
index ec0533e..7dc527a 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
@@ -17,6 +17,7 @@
 
 import android.annotation.SystemApi;
 import android.app.SystemServiceRegistry;
+import android.app.appsearch.aidl.IAppSearchManager;
 import android.content.Context;
 
 /**
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchMigrationHelper.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchMigrationHelper.java
index 0089c6d..353051c 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchMigrationHelper.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchMigrationHelper.java
@@ -23,6 +23,9 @@
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
+import android.app.appsearch.aidl.AppSearchResultParcel;
+import android.app.appsearch.aidl.IAppSearchManager;
+import android.app.appsearch.aidl.IAppSearchResultCallback;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -105,8 +108,8 @@
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
                         @Override
-                        public void onResult(AppSearchResult result) {
-                            future.complete(result);
+                        public void onResult(AppSearchResultParcel resultParcel) {
+                            future.complete(resultParcel.getResult());
                         }
                     });
             AppSearchResult<Void> result = future.get();
@@ -145,8 +148,8 @@
             mService.putDocumentsFromFile(mPackageName, mDatabaseName, fileDescriptor, mUserId,
                     new IAppSearchResultCallback.Stub() {
                         @Override
-                        public void onResult(AppSearchResult result) {
-                            future.complete(result);
+                        public void onResult(AppSearchResultParcel resultParcel) {
+                            future.complete(resultParcel.getResult());
                         }
                     });
             AppSearchResult<List<Bundle>> result = future.get();
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index c112d0e..5910130 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -19,6 +19,11 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.appsearch.aidl.AppSearchBatchResultParcel;
+import android.app.appsearch.aidl.AppSearchResultParcel;
+import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
+import android.app.appsearch.aidl.IAppSearchManager;
+import android.app.appsearch.aidl.IAppSearchResultCallback;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.app.appsearch.util.SchemaMigrationUtil;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -86,13 +91,15 @@
             @NonNull Consumer<AppSearchResult<AppSearchSession>> callback) {
         try {
             mService.initialize(mUserId, new IAppSearchResultCallback.Stub() {
-                public void onResult(AppSearchResult result) {
+                @Override
+                public void onResult(AppSearchResultParcel resultParcel) {
                     executor.execute(() -> {
+                        AppSearchResult<Void> result = resultParcel.getResult();
                         if (result.isSuccess()) {
                             callback.accept(
                                     AppSearchResult.newSuccessfulResult(AppSearchSession.this));
                         } else {
-                            callback.accept(result);
+                            callback.accept(AppSearchResult.newFailedResult(result));
                         }
                     });
                 }
@@ -191,15 +198,16 @@
                     mDatabaseName,
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
                             executor.execute(() -> {
+                                AppSearchResult<Bundle> result = resultParcel.getResult();
                                 if (result.isSuccess()) {
-                                    Bundle responseBundle = (Bundle) result.getResultValue();
                                     GetSchemaResponse response =
-                                            new GetSchemaResponse(responseBundle);
+                                            new GetSchemaResponse(result.getResultValue());
                                     callback.accept(AppSearchResult.newSuccessfulResult(response));
                                 } else {
-                                    callback.accept(result);
+                                    callback.accept(AppSearchResult.newFailedResult(result));
                                 }
                             });
                         }
@@ -227,15 +235,17 @@
                     mDatabaseName,
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
                             executor.execute(() -> {
+                                AppSearchResult<List<String>> result = resultParcel.getResult();
                                 if (result.isSuccess()) {
                                     Set<String> namespaces =
-                                            new ArraySet<>((List<String>) result.getResultValue());
+                                            new ArraySet<>(result.getResultValue());
                                     callback.accept(
                                             AppSearchResult.newSuccessfulResult(namespaces));
                                 } else {
-                                    callback.accept(result);
+                                    callback.accept(AppSearchResult.newFailedResult(result));
                                 }
                             });
                         }
@@ -280,13 +290,14 @@
                     /*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
                     new IAppSearchBatchResultCallback.Stub() {
                         @Override
-                        public void onResult(AppSearchBatchResult result) {
-                            executor.execute(() -> callback.onResult(result));
+                        public void onResult(AppSearchBatchResultParcel resultParcel) {
+                            executor.execute(() -> callback.onResult(resultParcel.getResult()));
                         }
 
                         @Override
-                        public void onSystemError(AppSearchResult result) {
-                            executor.execute(() -> sendSystemErrorToCallback(result, callback));
+                        public void onSystemError(AppSearchResultParcel resultParcel) {
+                            executor.execute(() -> sendSystemErrorToCallback(
+                                    resultParcel.getResult(), callback));
                         }
                     });
             mIsMutated = true;
@@ -341,15 +352,17 @@
                     mUserId,
                     new IAppSearchBatchResultCallback.Stub() {
                         @Override
-                        public void onResult(AppSearchBatchResult result) {
+                        public void onResult(AppSearchBatchResultParcel resultParcel) {
                             executor.execute(() -> {
+                                AppSearchBatchResult<String, Bundle> result =
+                                        resultParcel.getResult();
                                 AppSearchBatchResult.Builder<String, GenericDocument>
                                         documentResultBuilder =
                                         new AppSearchBatchResult.Builder<>();
 
                                 // Translate successful results
                                 for (Map.Entry<String, Bundle> bundleEntry :
-                                        ((Map<String, Bundle>) result.getSuccesses()).entrySet()) {
+                                        result.getSuccesses().entrySet()) {
                                     GenericDocument document;
                                     try {
                                         document = new GenericDocument(bundleEntry.getValue());
@@ -380,8 +393,9 @@
                         }
 
                         @Override
-                        public void onSystemError(AppSearchResult result) {
-                            executor.execute(() -> sendSystemErrorToCallback(result, callback));
+                        public void onSystemError(AppSearchResultParcel result) {
+                            executor.execute(
+                                    () -> sendSystemErrorToCallback(result.getResult(), callback));
                         }
                     });
         } catch (RemoteException e) {
@@ -492,8 +506,9 @@
                     /*systemUsage=*/ false,
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
-                            executor.execute(() -> callback.accept(result));
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
+                            executor.execute(() -> callback.accept(resultParcel.getResult()));
                         }
                     });
             mIsMutated = true;
@@ -550,13 +565,14 @@
                     new ArrayList<>(request.getIds()), mUserId,
                     new IAppSearchBatchResultCallback.Stub() {
                         @Override
-                        public void onResult(AppSearchBatchResult result) {
-                            executor.execute(() -> callback.onResult(result));
+                        public void onResult(AppSearchBatchResultParcel resultParcel) {
+                            executor.execute(() -> callback.onResult(resultParcel.getResult()));
                         }
 
                         @Override
-                        public void onSystemError(AppSearchResult result) {
-                            executor.execute(() -> sendSystemErrorToCallback(result, callback));
+                        public void onSystemError(AppSearchResultParcel resultParcel) {
+                            executor.execute(() -> sendSystemErrorToCallback(
+                                    resultParcel.getResult(), callback));
                         }
                     });
             mIsMutated = true;
@@ -598,8 +614,9 @@
             mService.removeByQuery(mPackageName, mDatabaseName, queryExpression,
                     searchSpec.getBundle(), mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
-                            executor.execute(() -> callback.accept(result));
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
+                            executor.execute(() -> callback.accept(resultParcel.getResult()));
                         }
                     });
             mIsMutated = true;
@@ -629,15 +646,15 @@
                     mDatabaseName,
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
                             executor.execute(() -> {
+                                AppSearchResult<Bundle> result = resultParcel.getResult();
                                 if (result.isSuccess()) {
-                                    Bundle responseBundle = (Bundle) result.getResultValue();
-                                    StorageInfo response =
-                                            new StorageInfo(responseBundle);
+                                    StorageInfo response = new StorageInfo(result.getResultValue());
                                     callback.accept(AppSearchResult.newSuccessfulResult(response));
                                 } else {
-                                    callback.accept(result);
+                                    callback.accept(AppSearchResult.newFailedResult(result));
                                 }
                             });
                         }
@@ -686,13 +703,14 @@
                     request.getVersion(),
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
                             executor.execute(() -> {
+                                AppSearchResult<Bundle> result = resultParcel.getResult();
                                 if (result.isSuccess()) {
                                     try {
                                         SetSchemaResponse setSchemaResponse =
-                                                new SetSchemaResponse(
-                                                        (Bundle) result.getResultValue());
+                                                new SetSchemaResponse(result.getResultValue());
                                         if (!request.isForceOverride()) {
                                             // Throw exception if there is any deleted types or
                                             // incompatible types. That's the only case we swallowed
@@ -707,7 +725,7 @@
                                         callback.accept(AppSearchResult.throwableToFailedResult(t));
                                     }
                                 } else {
-                                    callback.accept(result);
+                                    callback.accept(AppSearchResult.newFailedResult(result));
                                 }
                             });
                         }
@@ -772,8 +790,9 @@
                         request.getVersion(),
                         mUserId,
                         new IAppSearchResultCallback.Stub() {
-                            public void onResult(AppSearchResult result) {
-                                setSchemaFuture.complete(result);
+                            @Override
+                            public void onResult(AppSearchResultParcel resultParcel) {
+                                setSchemaFuture.complete(resultParcel.getResult());
                             }
                         });
                 AppSearchResult<Bundle> setSchemaResult = setSchemaFuture.get();
@@ -823,8 +842,8 @@
                                 mUserId,
                                 new IAppSearchResultCallback.Stub() {
                                     @Override
-                                    public void onResult(AppSearchResult result) {
-                                        setSchema2Future.complete(result);
+                                    public void onResult(AppSearchResultParcel resultParcel) {
+                                        setSchema2Future.complete(resultParcel.getResult());
                                     }
                                 });
                         AppSearchResult<Bundle> setSchema2Result = setSchema2Future.get();
diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
index d49f472..7d246c2 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
@@ -16,10 +16,12 @@
 
 package android.app.appsearch;
 
-
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.appsearch.aidl.AppSearchResultParcel;
+import android.app.appsearch.aidl.IAppSearchManager;
+import android.app.appsearch.aidl.IAppSearchResultCallback;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -71,13 +73,15 @@
             @NonNull Consumer<AppSearchResult<GlobalSearchSession>> callback) {
         try {
             mService.initialize(mUserId, new IAppSearchResultCallback.Stub() {
-                public void onResult(AppSearchResult result) {
+                @Override
+                public void onResult(AppSearchResultParcel resultParcel) {
                     executor.execute(() -> {
+                        AppSearchResult<Void> result = resultParcel.getResult();
                         if (result.isSuccess()) {
                             callback.accept(
                                     AppSearchResult.newSuccessfulResult(GlobalSearchSession.this));
                         } else {
-                            callback.accept(result);
+                            callback.accept(AppSearchResult.newFailedResult(result));
                         }
                     });
                 }
@@ -159,8 +163,9 @@
                     /*systemUsage=*/ true,
                     mUserId,
                     new IAppSearchResultCallback.Stub() {
-                        public void onResult(AppSearchResult result) {
-                            executor.execute(() -> callback.accept(result));
+                        @Override
+                        public void onResult(AppSearchResultParcel resultParcel) {
+                            executor.execute(() -> callback.accept(resultParcel.getResult()));
                         }
                     });
             mIsMutated = true;
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
index 531c984..4368672 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
@@ -20,6 +20,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.appsearch.aidl.AppSearchResultParcel;
+import android.app.appsearch.aidl.IAppSearchManager;
+import android.app.appsearch.aidl.IAppSearchResultCallback;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Log;
@@ -139,18 +142,20 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
         return new IAppSearchResultCallback.Stub() {
-            public void onResult(AppSearchResult result) {
-                executor.execute(() -> invokeCallback(result, callback));
+            @Override
+            public void onResult(AppSearchResultParcel resultParcel) {
+                executor.execute(() -> invokeCallback(resultParcel.getResult(), callback));
             }
         };
     }
 
-    private void invokeCallback(AppSearchResult result,
+    private void invokeCallback(
+            @NonNull AppSearchResult<Bundle> searchResultPageResult,
             @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
-        if (result.isSuccess()) {
+        if (searchResultPageResult.isSuccess()) {
             try {
                 SearchResultPage searchResultPage =
-                        new SearchResultPage((Bundle) result.getResultValue());
+                        new SearchResultPage(searchResultPageResult.getResultValue());
                 mNextPageToken = searchResultPage.getNextPageToken();
                 callback.accept(AppSearchResult.newSuccessfulResult(
                         searchResultPage.getResults()));
@@ -158,7 +163,7 @@
                 callback.accept(AppSearchResult.throwableToFailedResult(t));
             }
         } else {
-            callback.accept(result);
+            callback.accept(AppSearchResult.newFailedResult(searchResultPageResult));
         }
     }
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.aidl b/apex/appsearch/framework/java/android/app/appsearch/aidl/AppSearchBatchResultParcel.aidl
similarity index 80%
copy from apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.aidl
copy to apex/appsearch/framework/java/android/app/appsearch/aidl/AppSearchBatchResultParcel.aidl
index 37ce990..89908cd 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/aidl/AppSearchBatchResultParcel.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright 2020, The Android Open Source Project
+ * Copyright 2021, 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app.appsearch;
+package android.app.appsearch.aidl;
 
 /** {@hide} */
-parcelable AppSearchResult<ValueType>;
\ No newline at end of file
+parcelable AppSearchBatchResultParcel<ValueType>;
diff --git a/apex/appsearch/framework/java/android/app/appsearch/aidl/AppSearchBatchResultParcel.java b/apex/appsearch/framework/java/android/app/appsearch/aidl/AppSearchBatchResultParcel.java
new file mode 100644
index 0000000..b0cc10c
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/aidl/AppSearchBatchResultParcel.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.aidl;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchResult;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Parcelable wrapper around {@link AppSearchBatchResult}.
+ *
+ * <p>{@link AppSearchBatchResult} can contain any type of key and value, including non-parcelable
+ * values. For the specific case of sending {@link AppSearchBatchResult} across Binder, this class
+ * wraps an {@link AppSearchBatchResult} that has String keys and Parcelable values. It provides
+ * parcelability of the whole structure.
+ *
+ * @param <ValueType> The type of result object for successful calls. Must be a parcelable type.
+ * @hide
+ */
+public final class AppSearchBatchResultParcel<ValueType> implements Parcelable {
+    private final AppSearchBatchResult<String, ValueType> mResult;
+
+    /** Creates a new {@link AppSearchBatchResultParcel} from the given result. */
+    public AppSearchBatchResultParcel(@NonNull AppSearchBatchResult<String, ValueType> result) {
+        mResult = Objects.requireNonNull(result);
+    }
+
+    private AppSearchBatchResultParcel(@NonNull Parcel in) {
+        Bundle bundle = in.readBundle();
+        AppSearchBatchResult.Builder<String, ValueType> builder =
+                new AppSearchBatchResult.Builder<>();
+        for (String key : bundle.keySet()) {
+            AppSearchResultParcel<ValueType> resultParcel = bundle.getParcelable(key);
+            builder.setResult(key, resultParcel.getResult());
+        }
+        mResult = builder.build();
+    }
+
+    @NonNull
+    public AppSearchBatchResult<String, ValueType> getResult() {
+        return mResult;
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        Bundle bundle = new Bundle();
+        for (Map.Entry<String, AppSearchResult<ValueType>> entry
+                : mResult.getAll().entrySet()) {
+            bundle.putParcelable(entry.getKey(), new AppSearchResultParcel<>(entry.getValue()));
+        }
+        dest.writeBundle(bundle);
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @NonNull
+    public static final Creator<AppSearchBatchResultParcel<?>> CREATOR =
+            new Creator<AppSearchBatchResultParcel<?>>() {
+                @NonNull
+                @Override
+                public AppSearchBatchResultParcel<?> createFromParcel(@NonNull Parcel in) {
+                    return new AppSearchBatchResultParcel<>(in);
+                }
+
+                @NonNull
+                @Override
+                public AppSearchBatchResultParcel<?>[] newArray(int size) {
+                    return new AppSearchBatchResultParcel<?>[size];
+                }
+            };
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.aidl b/apex/appsearch/framework/java/android/app/appsearch/aidl/AppSearchResultParcel.aidl
similarity index 81%
rename from apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.aidl
rename to apex/appsearch/framework/java/android/app/appsearch/aidl/AppSearchResultParcel.aidl
index 37ce990..4e35bd5 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/aidl/AppSearchResultParcel.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright 2020, The Android Open Source Project
+ * Copyright 2021, 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app.appsearch;
+package android.app.appsearch.aidl;
 
 /** {@hide} */
-parcelable AppSearchResult<ValueType>;
\ No newline at end of file
+parcelable AppSearchResultParcel<ValueType>;
diff --git a/apex/appsearch/framework/java/android/app/appsearch/aidl/AppSearchResultParcel.java b/apex/appsearch/framework/java/android/app/appsearch/aidl/AppSearchResultParcel.java
new file mode 100644
index 0000000..8b137d3
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/aidl/AppSearchResultParcel.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.aidl;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Parcelable wrapper around {@link AppSearchResult}.
+ *
+ * <p>{@link AppSearchResult} can contain any value, including non-parcelable values. For the
+ * specific case of sending {@link AppSearchResult} across Binder, this class wraps an
+ * {@link AppSearchResult} that contains a parcelable type and provides parcelability of the whole
+ * structure.
+ *
+ * @param <ValueType> The type of result object for successful calls. Must be a parcelable type.
+ * @hide
+ */
+public final class AppSearchResultParcel<ValueType> implements Parcelable {
+    private final AppSearchResult<ValueType> mResult;
+
+    /** Creates a new {@link AppSearchResultParcel} from the given result. */
+    public AppSearchResultParcel(@NonNull AppSearchResult<ValueType> result) {
+        mResult = Objects.requireNonNull(result);
+    }
+
+    private AppSearchResultParcel(@NonNull Parcel in) {
+        int resultCode = in.readInt();
+        ValueType resultValue = (ValueType) in.readValue(/*loader=*/ null);
+        String errorMessage = in.readString();
+        if (resultCode == AppSearchResult.RESULT_OK) {
+            mResult = AppSearchResult.newSuccessfulResult(resultValue);
+        } else {
+            mResult = AppSearchResult.newFailedResult(resultCode, errorMessage);
+        }
+    }
+
+    @NonNull
+    public AppSearchResult<ValueType> getResult() {
+        return mResult;
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mResult.getResultCode());
+        if (mResult.isSuccess()) {
+            dest.writeValue(mResult.getResultValue());
+        } else {
+            dest.writeValue(null);
+        }
+        dest.writeString(mResult.getErrorMessage());
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @NonNull
+    public static final Creator<AppSearchResultParcel<?>> CREATOR =
+            new Creator<AppSearchResultParcel<?>>() {
+                @NonNull
+                @Override
+                public AppSearchResultParcel<?> createFromParcel(@NonNull Parcel in) {
+                    return new AppSearchResultParcel<>(in);
+                }
+
+                @NonNull
+                @Override
+                public AppSearchResultParcel<?>[] newArray(int size) {
+                    return new AppSearchResultParcel<?>[size];
+                }
+            };
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl b/apex/appsearch/framework/java/android/app/appsearch/aidl/IAppSearchBatchResultCallback.aidl
similarity index 70%
rename from apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
rename to apex/appsearch/framework/java/android/app/appsearch/aidl/IAppSearchBatchResultCallback.aidl
index 64b331e..1fe19cc 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/aidl/IAppSearchBatchResultCallback.aidl
@@ -13,13 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app.appsearch;
+package android.app.appsearch.aidl;
 
-import android.app.appsearch.AppSearchBatchResult;
-import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.aidl.AppSearchBatchResultParcel;
+import android.app.appsearch.aidl.AppSearchResultParcel;
 
 /** {@hide} */
 oneway interface IAppSearchBatchResultCallback {
-    void onResult(in AppSearchBatchResult result);
-    void onSystemError(in AppSearchResult result);
+    void onResult(in AppSearchBatchResultParcel resultParcel);
+    void onSystemError(in AppSearchResultParcel resultParcel);
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/aidl/IAppSearchManager.aidl
similarity index 98%
rename from apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
rename to apex/appsearch/framework/java/android/app/appsearch/aidl/IAppSearchManager.aidl
index 507bd68..6f7e82e 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/aidl/IAppSearchManager.aidl
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app.appsearch;
+package android.app.appsearch.aidl;
 
 import android.os.Bundle;
 
-import android.app.appsearch.IAppSearchBatchResultCallback;
-import android.app.appsearch.IAppSearchResultCallback;
+import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
+import android.app.appsearch.aidl.IAppSearchResultCallback;
 import android.os.ParcelFileDescriptor;
 
 /** {@hide} */
@@ -200,7 +200,7 @@
     * @param searchSpecBundle SearchSpec bundle.
     * @param userId Id of the calling user.
     * @param callback {@link IAppSearchResultCallback#onResult} will be called with an
-    *        {@link AppSearchResult}&lt;{@code null}&gt;.
+    *        {@link AppSearchResult}&lt;{@code Void}&gt;.
     */
     void writeQueryResultsToFile(
         in String packageName,
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl b/apex/appsearch/framework/java/android/app/appsearch/aidl/IAppSearchResultCallback.aidl
similarity index 81%
rename from apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
rename to apex/appsearch/framework/java/android/app/appsearch/aidl/IAppSearchResultCallback.aidl
index 299c9957..097f0d1 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/aidl/IAppSearchResultCallback.aidl
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app.appsearch;
+package android.app.appsearch.aidl;
 
-import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.aidl.AppSearchResultParcel;
 
 /** {@hide} */
 oneway interface IAppSearchResultCallback {
-    void onResult(in AppSearchResult result);
+    void onResult(in AppSearchResultParcel resultParcel);
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchBatchResult.java
similarity index 75%
rename from apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
rename to apex/appsearch/framework/java/external/android/app/appsearch/AppSearchBatchResult.java
index 9ae0d62..aca0fc0 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchBatchResult.java
@@ -13,37 +13,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package android.app.appsearch;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.util.ArrayMap;
 
 import com.android.internal.util.Preconditions;
 
-import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
 
 /**
  * Provides results for AppSearch batch operations which encompass multiple documents.
  *
- * <p>Individual results of a batch operation are separated into two maps: one for successes and
- * one for failures. For successes, {@link #getSuccesses()} will return a map of keys to
- * instances of the value type. For failures, {@link #getFailures()} will return a map of keys to
- * {@link AppSearchResult} objects.
+ * <p>Individual results of a batch operation are separated into two maps: one for successes and one
+ * for failures. For successes, {@link #getSuccesses()} will return a map of keys to instances of
+ * the value type. For failures, {@link #getFailures()} will return a map of keys to {@link
+ * AppSearchResult} objects.
  *
  * <p>Alternatively, {@link #getAll()} returns a map of keys to {@link AppSearchResult} objects for
  * both successes and failures.
  *
+ * @param <KeyType> The type of the keys for which the results will be reported.
+ * @param <ValueType> The type of the result objects for successful results.
  * @see AppSearchSession#put
  * @see AppSearchSession#getByDocumentId
  * @see AppSearchSession#remove
  */
-public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelable {
+public final class AppSearchBatchResult<KeyType, ValueType> {
     @NonNull private final Map<KeyType, ValueType> mSuccesses;
     @NonNull private final Map<KeyType, AppSearchResult<ValueType>> mFailures;
     @NonNull private final Map<KeyType, AppSearchResult<ValueType>> mAll;
@@ -57,27 +55,6 @@
         mAll = all;
     }
 
-    private AppSearchBatchResult(@NonNull Parcel in) {
-        mAll = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
-        Map<KeyType, ValueType> successes = new ArrayMap<>();
-        Map<KeyType, AppSearchResult<ValueType>> failures = new ArrayMap<>();
-        for (Map.Entry<KeyType, AppSearchResult<ValueType>> entry : mAll.entrySet()) {
-            if (entry.getValue().isSuccess()) {
-                successes.put(entry.getKey(), entry.getValue().getResultValue());
-            } else {
-                failures.put(entry.getKey(), entry.getValue());
-            }
-        }
-        mSuccesses = Collections.unmodifiableMap(successes);
-        mFailures = Collections.unmodifiableMap(failures);
-    }
-
-    /** @hide */
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeMap(mAll);
-    }
-
     /** Returns {@code true} if this {@link AppSearchBatchResult} has no failures. */
     public boolean isSuccess() {
         return mFailures.isEmpty();
@@ -99,8 +76,8 @@
     }
 
     /**
-     * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all
-     * failed individual results.
+     * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all failed
+     * individual results.
      *
      * <p>The values of the {@link Map} will not be {@code null}.
      */
@@ -122,6 +99,7 @@
 
     /**
      * Asserts that this {@link AppSearchBatchResult} has no failures.
+     *
      * @hide
      */
     public void checkSuccess() {
@@ -136,33 +114,13 @@
         return "{\n  successes: " + mSuccesses + "\n  failures: " + mFailures + "\n}";
     }
 
-    /** @hide */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** @hide */
-    @NonNull
-    public static final Creator<AppSearchBatchResult> CREATOR =
-            new Creator<AppSearchBatchResult>() {
-        @NonNull
-        @Override
-        public AppSearchBatchResult createFromParcel(@NonNull Parcel in) {
-            return new AppSearchBatchResult(in);
-        }
-
-        @NonNull
-        @Override
-        public AppSearchBatchResult[] newArray(int size) {
-            return new AppSearchBatchResult[size];
-        }
-    };
-
     /**
      * Builder for {@link AppSearchBatchResult} objects.
      *
      * <p>Once {@link #build} is called, the instance can no longer be used.
+     *
+     * @param <KeyType> The type of the keys for which the results will be reported.
+     * @param <ValueType> The type of the result objects for successful results.
      */
     public static final class Builder<KeyType, ValueType> {
         private final Map<KeyType, ValueType> mSuccesses = new ArrayMap<>();
@@ -177,7 +135,7 @@
          *
          * @throws IllegalStateException if the builder has already been used.
          */
-        @SuppressWarnings("MissingGetterMatchingBuilder")  // See getSuccesses
+        @SuppressWarnings("MissingGetterMatchingBuilder") // See getSuccesses
         @NonNull
         public Builder<KeyType, ValueType> setSuccess(
                 @NonNull KeyType key, @Nullable ValueType result) {
@@ -193,7 +151,7 @@
          *
          * @throws IllegalStateException if the builder has already been used.
          */
-        @SuppressWarnings("MissingGetterMatchingBuilder")  // See getFailures
+        @SuppressWarnings("MissingGetterMatchingBuilder") // See getFailures
         @NonNull
         public Builder<KeyType, ValueType> setFailure(
                 @NonNull KeyType key,
@@ -211,7 +169,7 @@
          *
          * @throws IllegalStateException if the builder has already been used.
          */
-        @SuppressWarnings("MissingGetterMatchingBuilder")  // See getAll
+        @SuppressWarnings("MissingGetterMatchingBuilder") // See getAll
         @NonNull
         public Builder<KeyType, ValueType> setResult(
                 @NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchResult.java
similarity index 82%
rename from apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
rename to apex/appsearch/framework/java/external/android/app/appsearch/AppSearchResult.java
index b06e215..30c98b0 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchResult.java
@@ -13,15 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package android.app.appsearch;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.appsearch.exceptions.AppSearchException;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.util.Log;
 
 import com.android.internal.util.Preconditions;
@@ -36,24 +33,26 @@
  *
  * @param <ValueType> The type of result object for successful calls.
  */
-public final class AppSearchResult<ValueType> implements Parcelable {
+public final class AppSearchResult<ValueType> {
     private static final String TAG = "AppSearchResult";
 
     /**
      * Result codes from {@link AppSearchSession} methods.
+     *
      * @hide
      */
-    @IntDef(value = {
-            RESULT_OK,
-            RESULT_UNKNOWN_ERROR,
-            RESULT_INTERNAL_ERROR,
-            RESULT_INVALID_ARGUMENT,
-            RESULT_IO_ERROR,
-            RESULT_OUT_OF_SPACE,
-            RESULT_NOT_FOUND,
-            RESULT_INVALID_SCHEMA,
-            RESULT_SECURITY_ERROR,
-    })
+    @IntDef(
+            value = {
+                RESULT_OK,
+                RESULT_UNKNOWN_ERROR,
+                RESULT_INTERNAL_ERROR,
+                RESULT_INVALID_ARGUMENT,
+                RESULT_IO_ERROR,
+                RESULT_OUT_OF_SPACE,
+                RESULT_NOT_FOUND,
+                RESULT_INVALID_SCHEMA,
+                RESULT_SECURITY_ERROR,
+            })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResultCode {}
 
@@ -109,20 +108,6 @@
         mErrorMessage = errorMessage;
     }
 
-    private AppSearchResult(@NonNull Parcel in) {
-        mResultCode = in.readInt();
-        mResultValue = (ValueType) in.readValue(/*loader=*/ null);
-        mErrorMessage = in.readString();
-    }
-
-    /** @hide */
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mResultCode);
-        dest.writeValue(mResultValue);
-        dest.writeString(mErrorMessage);
-    }
-
     /** Returns {@code true} if {@link #getResultCode} equals {@link AppSearchResult#RESULT_OK}. */
     public boolean isSuccess() {
         return getResultCode() == RESULT_OK;
@@ -154,8 +139,8 @@
      *
      * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error
      * message may be {@code null} even if {@link #isSuccess} is {@code false}. See the
-     * documentation of the particular {@link AppSearchSession} call producing this
-     * {@link AppSearchResult} for what is returned by {@link #getErrorMessage}.
+     * documentation of the particular {@link AppSearchSession} call producing this {@link
+     * AppSearchResult} for what is returned by {@link #getErrorMessage}.
      */
     @Nullable
     public String getErrorMessage() {
@@ -190,40 +175,14 @@
         return "[FAILURE(" + mResultCode + ")]: " + mErrorMessage;
     }
 
-    /** @hide */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** @hide */
-    @NonNull
-    public static final Creator<AppSearchResult> CREATOR = new Creator<AppSearchResult>() {
-        @NonNull
-        @Override
-        public AppSearchResult createFromParcel(@NonNull Parcel in) {
-            return new AppSearchResult(in);
-        }
-
-        @NonNull
-        @Override
-        public AppSearchResult[] newArray(int size) {
-            return new AppSearchResult[size];
-        }
-    };
-
-    /**
-     * Creates a new successful {@link AppSearchResult}.
-     */
+    /** Creates a new successful {@link AppSearchResult}. */
     @NonNull
     public static <ValueType> AppSearchResult<ValueType> newSuccessfulResult(
             @Nullable ValueType value) {
         return new AppSearchResult<>(RESULT_OK, value, /*errorMessage=*/ null);
     }
 
-    /**
-     * Creates a new failed {@link AppSearchResult}.
-     */
+    /** Creates a new failed {@link AppSearchResult}. */
     @NonNull
     public static <ValueType> AppSearchResult<ValueType> newFailedResult(
             @ResultCode int resultCode, @Nullable String errorMessage) {
@@ -238,7 +197,8 @@
     @NonNull
     public static <ValueType> AppSearchResult<ValueType> newFailedResult(
             @NonNull AppSearchResult<?> otherFailedResult) {
-        Preconditions.checkState(!otherFailedResult.isSuccess(),
+        Preconditions.checkState(
+                !otherFailedResult.isSuccess(),
                 "Cannot convert a success result to a failed result");
         return AppSearchResult.newFailedResult(
                 otherFailedResult.getResultCode(), otherFailedResult.getErrorMessage());
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
index 4378a98..c1fcd6c 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -120,7 +120,7 @@
     /** Builder for {@link AppSearchSchema objects}. */
     public static final class Builder {
         private final String mSchemaType;
-        private final ArrayList<Bundle> mPropertyBundles = new ArrayList<>();
+        private ArrayList<Bundle> mPropertyBundles = new ArrayList<>();
         private final Set<String> mPropertyNames = new ArraySet<>();
         private boolean mBuilt = false;
 
@@ -133,8 +133,8 @@
         /** Adds a property to the given type. */
         @NonNull
         public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Objects.requireNonNull(propertyConfig);
+            resetIfBuilt();
             String name = propertyConfig.getName();
             if (!mPropertyNames.add(name)) {
                 throw new IllegalSchemaException("Property defined more than once: " + name);
@@ -143,20 +143,22 @@
             return this;
         }
 
-        /**
-         * Constructs a new {@link AppSearchSchema} from the contents of this builder.
-         *
-         * <p>After calling this method, the builder must no longer be used.
-         */
+        /** Constructs a new {@link AppSearchSchema} from the contents of this builder. */
         @NonNull
         public AppSearchSchema build() {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Bundle bundle = new Bundle();
             bundle.putString(AppSearchSchema.SCHEMA_TYPE_FIELD, mSchemaType);
             bundle.putParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD, mPropertyBundles);
             mBuilt = true;
             return new AppSearchSchema(bundle);
         }
+
+        private void resetIfBuilt() {
+            if (mBuilt) {
+                mPropertyBundles = new ArrayList<>(mPropertyBundles);
+                mBuilt = false;
+            }
+        }
     }
 
     /**
@@ -251,6 +253,7 @@
         }
 
         @Override
+        @NonNull
         public String toString() {
             return mBundle.toString();
         }
@@ -410,16 +413,14 @@
 
         /** Builder for {@link StringPropertyConfig}. */
         public static final class Builder {
-            private final Bundle mBundle = new Bundle();
-            private boolean mBuilt = false;
+            private final String mPropertyName;
+            private @Cardinality int mCardinality = CARDINALITY_OPTIONAL;
+            private @IndexingType int mIndexingType = INDEXING_TYPE_NONE;
+            private @TokenizerType int mTokenizerType = TOKENIZER_TYPE_NONE;
 
             /** Creates a new {@link StringPropertyConfig.Builder}. */
             public Builder(@NonNull String propertyName) {
-                mBundle.putString(NAME_FIELD, propertyName);
-                mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_STRING);
-                mBundle.putInt(CARDINALITY_FIELD, CARDINALITY_OPTIONAL);
-                mBundle.putInt(INDEXING_TYPE_FIELD, INDEXING_TYPE_NONE);
-                mBundle.putInt(TOKENIZER_TYPE_FIELD, TOKENIZER_TYPE_NONE);
+                mPropertyName = Objects.requireNonNull(propertyName);
             }
 
             /**
@@ -431,10 +432,9 @@
             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
             @NonNull
             public StringPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
                 Preconditions.checkArgumentInRange(
                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
-                mBundle.putInt(CARDINALITY_FIELD, cardinality);
+                mCardinality = cardinality;
                 return this;
             }
 
@@ -446,10 +446,9 @@
              */
             @NonNull
             public StringPropertyConfig.Builder setIndexingType(@IndexingType int indexingType) {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
                 Preconditions.checkArgumentInRange(
                         indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_PREFIXES, "indexingType");
-                mBundle.putInt(INDEXING_TYPE_FIELD, indexingType);
+                mIndexingType = indexingType;
                 return this;
             }
 
@@ -466,25 +465,22 @@
              */
             @NonNull
             public StringPropertyConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
                 Preconditions.checkArgumentInRange(
                         tokenizerType, TOKENIZER_TYPE_NONE, TOKENIZER_TYPE_PLAIN, "tokenizerType");
-                mBundle.putInt(TOKENIZER_TYPE_FIELD, tokenizerType);
+                mTokenizerType = tokenizerType;
                 return this;
             }
 
-            /**
-             * Constructs a new {@link StringPropertyConfig} from the contents of this builder.
-             *
-             * <p>After calling this method, the builder must no longer be used.
-             *
-             * @throws IllegalStateException if the builder has already been used
-             */
+            /** Constructs a new {@link StringPropertyConfig} from the contents of this builder. */
             @NonNull
             public StringPropertyConfig build() {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
-                mBuilt = true;
-                return new StringPropertyConfig(mBundle);
+                Bundle bundle = new Bundle();
+                bundle.putString(NAME_FIELD, mPropertyName);
+                bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_STRING);
+                bundle.putInt(CARDINALITY_FIELD, mCardinality);
+                bundle.putInt(INDEXING_TYPE_FIELD, mIndexingType);
+                bundle.putInt(TOKENIZER_TYPE_FIELD, mTokenizerType);
+                return new StringPropertyConfig(bundle);
             }
         }
     }
@@ -497,14 +493,12 @@
 
         /** Builder for {@link Int64PropertyConfig}. */
         public static final class Builder {
-            private final Bundle mBundle = new Bundle();
-            private boolean mBuilt = false;
+            private final String mPropertyName;
+            private @Cardinality int mCardinality = CARDINALITY_OPTIONAL;
 
             /** Creates a new {@link Int64PropertyConfig.Builder}. */
             public Builder(@NonNull String propertyName) {
-                mBundle.putString(NAME_FIELD, propertyName);
-                mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_INT64);
-                mBundle.putInt(CARDINALITY_FIELD, CARDINALITY_OPTIONAL);
+                mPropertyName = Objects.requireNonNull(propertyName);
             }
 
             /**
@@ -516,25 +510,20 @@
             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
             @NonNull
             public Int64PropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
                 Preconditions.checkArgumentInRange(
                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
-                mBundle.putInt(CARDINALITY_FIELD, cardinality);
+                mCardinality = cardinality;
                 return this;
             }
 
-            /**
-             * Constructs a new {@link Int64PropertyConfig} from the contents of this builder.
-             *
-             * <p>After calling this method, the builder must no longer be used.
-             *
-             * @throws IllegalStateException if the builder has already been used
-             */
+            /** Constructs a new {@link Int64PropertyConfig} from the contents of this builder. */
             @NonNull
             public Int64PropertyConfig build() {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
-                mBuilt = true;
-                return new Int64PropertyConfig(mBundle);
+                Bundle bundle = new Bundle();
+                bundle.putString(NAME_FIELD, mPropertyName);
+                bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_INT64);
+                bundle.putInt(CARDINALITY_FIELD, mCardinality);
+                return new Int64PropertyConfig(bundle);
             }
         }
     }
@@ -547,14 +536,12 @@
 
         /** Builder for {@link DoublePropertyConfig}. */
         public static final class Builder {
-            private final Bundle mBundle = new Bundle();
-            private boolean mBuilt = false;
+            private final String mPropertyName;
+            private @Cardinality int mCardinality = CARDINALITY_OPTIONAL;
 
             /** Creates a new {@link DoublePropertyConfig.Builder}. */
             public Builder(@NonNull String propertyName) {
-                mBundle.putString(NAME_FIELD, propertyName);
-                mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_DOUBLE);
-                mBundle.putInt(CARDINALITY_FIELD, CARDINALITY_OPTIONAL);
+                mPropertyName = Objects.requireNonNull(propertyName);
             }
 
             /**
@@ -566,25 +553,20 @@
             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
             @NonNull
             public DoublePropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
                 Preconditions.checkArgumentInRange(
                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
-                mBundle.putInt(CARDINALITY_FIELD, cardinality);
+                mCardinality = cardinality;
                 return this;
             }
 
-            /**
-             * Constructs a new {@link DoublePropertyConfig} from the contents of this builder.
-             *
-             * <p>After calling this method, the builder must no longer be used.
-             *
-             * @throws IllegalStateException if the builder has already been used
-             */
+            /** Constructs a new {@link DoublePropertyConfig} from the contents of this builder. */
             @NonNull
             public DoublePropertyConfig build() {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
-                mBuilt = true;
-                return new DoublePropertyConfig(mBundle);
+                Bundle bundle = new Bundle();
+                bundle.putString(NAME_FIELD, mPropertyName);
+                bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_DOUBLE);
+                bundle.putInt(CARDINALITY_FIELD, mCardinality);
+                return new DoublePropertyConfig(bundle);
             }
         }
     }
@@ -597,14 +579,12 @@
 
         /** Builder for {@link BooleanPropertyConfig}. */
         public static final class Builder {
-            private final Bundle mBundle = new Bundle();
-            private boolean mBuilt = false;
+            private final String mPropertyName;
+            private @Cardinality int mCardinality = CARDINALITY_OPTIONAL;
 
             /** Creates a new {@link BooleanPropertyConfig.Builder}. */
             public Builder(@NonNull String propertyName) {
-                mBundle.putString(NAME_FIELD, propertyName);
-                mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_BOOLEAN);
-                mBundle.putInt(CARDINALITY_FIELD, CARDINALITY_OPTIONAL);
+                mPropertyName = Objects.requireNonNull(propertyName);
             }
 
             /**
@@ -616,25 +596,20 @@
             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
             @NonNull
             public BooleanPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
                 Preconditions.checkArgumentInRange(
                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
-                mBundle.putInt(CARDINALITY_FIELD, cardinality);
+                mCardinality = cardinality;
                 return this;
             }
 
-            /**
-             * Constructs a new {@link BooleanPropertyConfig} from the contents of this builder.
-             *
-             * <p>After calling this method, the builder must no longer be used.
-             *
-             * @throws IllegalStateException if the builder has already been used
-             */
+            /** Constructs a new {@link BooleanPropertyConfig} from the contents of this builder. */
             @NonNull
             public BooleanPropertyConfig build() {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
-                mBuilt = true;
-                return new BooleanPropertyConfig(mBundle);
+                Bundle bundle = new Bundle();
+                bundle.putString(NAME_FIELD, mPropertyName);
+                bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_BOOLEAN);
+                bundle.putInt(CARDINALITY_FIELD, mCardinality);
+                return new BooleanPropertyConfig(bundle);
             }
         }
     }
@@ -647,14 +622,12 @@
 
         /** Builder for {@link BytesPropertyConfig}. */
         public static final class Builder {
-            private final Bundle mBundle = new Bundle();
-            private boolean mBuilt = false;
+            private final String mPropertyName;
+            private @Cardinality int mCardinality = CARDINALITY_OPTIONAL;
 
             /** Creates a new {@link BytesPropertyConfig.Builder}. */
             public Builder(@NonNull String propertyName) {
-                mBundle.putString(NAME_FIELD, propertyName);
-                mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_BYTES);
-                mBundle.putInt(CARDINALITY_FIELD, CARDINALITY_OPTIONAL);
+                mPropertyName = Objects.requireNonNull(propertyName);
             }
 
             /**
@@ -666,25 +639,20 @@
             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
             @NonNull
             public BytesPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
                 Preconditions.checkArgumentInRange(
                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
-                mBundle.putInt(CARDINALITY_FIELD, cardinality);
+                mCardinality = cardinality;
                 return this;
             }
 
-            /**
-             * Constructs a new {@link BytesPropertyConfig} from the contents of this builder.
-             *
-             * <p>After calling this method, the builder must no longer be used.
-             *
-             * @throws IllegalStateException if the builder has already been used
-             */
+            /** Constructs a new {@link BytesPropertyConfig} from the contents of this builder. */
             @NonNull
             public BytesPropertyConfig build() {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
-                mBuilt = true;
-                return new BytesPropertyConfig(mBundle);
+                Bundle bundle = new Bundle();
+                bundle.putString(NAME_FIELD, mPropertyName);
+                bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_BYTES);
+                bundle.putInt(CARDINALITY_FIELD, mCardinality);
+                return new BytesPropertyConfig(bundle);
             }
         }
     }
@@ -715,20 +683,13 @@
             return mBundle.getBoolean(INDEX_NESTED_PROPERTIES_FIELD);
         }
 
-        /**
-         * Builder for {@link DocumentPropertyConfig}.
-         *
-         * <p>The following properties must be set, or {@link DocumentPropertyConfig} construction
-         * will fail:
-         *
-         * <ul>
-         *   <li>cardinality
-         *   <li>schemaType
-         * </ul>
-         */
+        /** Builder for {@link DocumentPropertyConfig}. */
         public static final class Builder {
-            private final Bundle mBundle = new Bundle();
-            private boolean mBuilt = false;
+            private final String mPropertyName;
+            // TODO(b/181887768): This should be final
+            private String mSchemaType;
+            private @Cardinality int mCardinality = CARDINALITY_OPTIONAL;
+            private boolean mShouldIndexNestedProperties = false;
 
             /**
              * Creates a new {@link DocumentPropertyConfig.Builder}.
@@ -740,11 +701,8 @@
              *     Documents of different types cannot be mixed into a single property.
              */
             public Builder(@NonNull String propertyName, @NonNull String schemaType) {
-                mBundle.putString(NAME_FIELD, propertyName);
-                mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_DOCUMENT);
-                mBundle.putInt(CARDINALITY_FIELD, CARDINALITY_OPTIONAL);
-                mBundle.putBoolean(INDEX_NESTED_PROPERTIES_FIELD, false);
-                mBundle.putString(SCHEMA_TYPE_FIELD, schemaType);
+                mPropertyName = Objects.requireNonNull(propertyName);
+                mSchemaType = Objects.requireNonNull(schemaType);
             }
 
             /**
@@ -754,10 +712,8 @@
             @Deprecated
             @UnsupportedAppUsage
             public Builder(@NonNull String propertyName) {
-                mBundle.putString(NAME_FIELD, propertyName);
-                mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_DOCUMENT);
-                mBundle.putInt(CARDINALITY_FIELD, CARDINALITY_OPTIONAL);
-                mBundle.putBoolean(INDEX_NESTED_PROPERTIES_FIELD, false);
+                mPropertyName = Objects.requireNonNull(propertyName);
+                mSchemaType = null;
             }
 
             /**
@@ -768,7 +724,7 @@
             @UnsupportedAppUsage
             @NonNull
             public Builder setSchemaType(@NonNull String schemaType) {
-                mBundle.putString(SCHEMA_TYPE_FIELD, schemaType);
+                mSchemaType = Objects.requireNonNull(schemaType);
                 return this;
             }
 
@@ -781,10 +737,9 @@
             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
             @NonNull
             public DocumentPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
                 Preconditions.checkArgumentInRange(
                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
-                mBundle.putInt(CARDINALITY_FIELD, cardinality);
+                mCardinality = cardinality;
                 return this;
             }
 
@@ -798,8 +753,7 @@
             @NonNull
             public DocumentPropertyConfig.Builder setShouldIndexNestedProperties(
                     boolean indexNestedProperties) {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
-                mBundle.putBoolean(INDEX_NESTED_PROPERTIES_FIELD, indexNestedProperties);
+                mShouldIndexNestedProperties = indexNestedProperties;
                 return this;
             }
 
@@ -815,19 +769,18 @@
                 return setShouldIndexNestedProperties(indexNestedProperties);
             }
 
-            /**
-             * Constructs a new {@link PropertyConfig} from the contents of this builder.
-             *
-             * <p>After calling this method, the builder must no longer be used.
-             *
-             * @throws IllegalStateException if the builder has already been used (e.g. missing
-             *     {@code dataType}).
-             */
+            /** Constructs a new {@link PropertyConfig} from the contents of this builder. */
             @NonNull
             public DocumentPropertyConfig build() {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
-                mBuilt = true;
-                return new DocumentPropertyConfig(mBundle);
+                Bundle bundle = new Bundle();
+                bundle.putString(NAME_FIELD, mPropertyName);
+                bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_DOCUMENT);
+                bundle.putInt(CARDINALITY_FIELD, mCardinality);
+                bundle.putBoolean(INDEX_NESTED_PROPERTIES_FIELD, mShouldIndexNestedProperties);
+                // TODO(b/181887768): Remove checkNotNull after the deprecated constructor (which
+                //  is the only way to get null here) is removed
+                bundle.putString(SCHEMA_TYPE_FIELD, Objects.requireNonNull(mSchemaType));
+                return new DocumentPropertyConfig(bundle);
             }
         }
     }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
index 39a4884..736deab 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -892,64 +892,129 @@
     @Override
     @NonNull
     public String toString() {
-        return bundleToString(mBundle).toString();
+        return formatGenericDocumentString(this, /*indentLevel=*/ 0);
     }
 
-    private static StringBuilder bundleToString(Bundle bundle) {
+    @NonNull
+    private static String formatGenericDocumentString(
+            @NonNull GenericDocument document, int indentLevel) {
         StringBuilder stringBuilder = new StringBuilder();
-        try {
-            String[] names = bundle.keySet().toArray(new String[0]);
-            // Sort names to make output deterministic. We need a custom comparator to handle
-            // nulls (arbitrarily putting them first, similar to Comparator.nullsFirst, which is
-            // only available since N).
-            Arrays.sort(
-                    names,
-                    (@Nullable String s1, @Nullable String s2) -> {
-                        if (s1 == null) {
-                            return s2 == null ? 0 : -1;
-                        } else if (s2 == null) {
-                            return 1;
-                        } else {
-                            return s1.compareTo(s2);
-                        }
-                    });
-            for (String name : names) {
-                stringBuilder.append("{ name: '").append(name).append("' value: ");
-                Object valueObject = bundle.get(name);
-                if (valueObject == null) {
-                    stringBuilder.append("<null>");
-                } else if (valueObject instanceof Bundle) {
-                    stringBuilder.append(bundleToString((Bundle) valueObject));
-                } else if (valueObject.getClass().isArray()) {
-                    stringBuilder.append("[ ");
-                    for (int i = 0; i < Array.getLength(valueObject); i++) {
-                        Object element = Array.get(valueObject, i);
-                        stringBuilder.append("'");
-                        if (element instanceof Bundle) {
-                            stringBuilder.append(bundleToString((Bundle) element));
-                        } else {
-                            stringBuilder.append(Array.get(valueObject, i));
-                        }
-                        stringBuilder.append("' ");
-                    }
-                    stringBuilder.append("]");
-                } else if (valueObject instanceof List) {
-                    @SuppressWarnings("unchecked")
-                    List<Bundle> bundles = (List<Bundle>) valueObject;
-                    for (int i = 0; i < bundles.size(); i++) {
-                        stringBuilder.append(bundleToString(bundles.get(i)));
-                    }
-                } else {
-                    stringBuilder.append(valueObject.toString());
-                }
-                stringBuilder.append(" } ");
+        stringBuilder.append(getIndent(indentLevel)).append("{\n");
+
+        String indentLevelOneString = getIndent(indentLevel + 1);
+
+        stringBuilder
+                .append(indentLevelOneString)
+                .append("namespace: \"")
+                .append(document.getNamespace())
+                .append("\",\n");
+
+        stringBuilder
+                .append(indentLevelOneString)
+                .append("id: \"")
+                .append(document.getId())
+                .append("\",\n");
+
+        stringBuilder
+                .append(indentLevelOneString)
+                .append("score: " + document.getScore())
+                .append(",\n");
+
+        stringBuilder
+                .append(indentLevelOneString)
+                .append("schemaType: \"")
+                .append(document.getSchemaType())
+                .append("\",\n");
+
+        stringBuilder
+                .append(indentLevelOneString)
+                .append("creationTimestampMillis: " + document.getCreationTimestampMillis())
+                .append(",\n");
+
+        stringBuilder
+                .append(indentLevelOneString)
+                .append("timeToLiveMillis: " + document.getTtlMillis())
+                .append(",\n");
+
+        stringBuilder.append(indentLevelOneString).append("properties: {\n");
+
+        int idx = 0;
+        for (String propertyName : document.getPropertyNames()) {
+            Object property = document.getProperty(propertyName);
+            stringBuilder
+                    .append(getIndent(indentLevel + 2))
+                    .append("\"")
+                    .append(propertyName)
+                    .append("\"")
+                    .append(": ");
+            stringBuilder.append(getPropertyString(property, indentLevel + 2));
+            if (idx != document.getPropertyNames().size() - 1) {
+                stringBuilder.append(",\n");
             }
-        } catch (RuntimeException e) {
-            // Catch any exceptions here since corrupt Bundles can throw different types of
-            // exceptions (e.g. b/38445840 & b/68937025).
-            stringBuilder.append("<error>");
+            ++idx;
         }
-        return stringBuilder;
+
+        stringBuilder.append("\n");
+        stringBuilder.append(indentLevelOneString).append("}");
+
+        stringBuilder.append("\n");
+        stringBuilder.append(getIndent(indentLevel)).append("}");
+
+        return stringBuilder.toString();
+    }
+
+    /**
+     * Creates string for property.
+     *
+     * @param property property object to create string for.
+     * @param indentLevel base indent level for property.
+     */
+    @NonNull
+    private static String getPropertyString(@NonNull Object property, int indentLevel) {
+        Objects.requireNonNull(property);
+
+        StringBuilder str = new StringBuilder("[");
+
+        if (property instanceof GenericDocument[]) {
+            GenericDocument[] documentValues = (GenericDocument[]) property;
+            for (int i = 0; i < documentValues.length; ++i) {
+                str.append("\n");
+                str.append(formatGenericDocumentString(documentValues[i], indentLevel + 1));
+                if (i != documentValues.length - 1) {
+                    str.append(", ");
+                }
+                str.append("\n");
+            }
+            str.append(getIndent(indentLevel));
+        } else {
+            int propertyArrLength = Array.getLength(property);
+            for (int i = 0; i < propertyArrLength; i++) {
+                Object propertyElement = Array.get(property, i);
+                if (propertyElement instanceof String) {
+                    str.append("\"").append(propertyElement).append("\"");
+                } else if (propertyElement instanceof byte[]) {
+                    str.append(Arrays.toString((byte[]) propertyElement));
+                } else {
+                    str.append(propertyElement);
+                }
+                if (i != propertyArrLength - 1) {
+                    str.append(", ");
+                }
+            }
+        }
+
+        str.append("]");
+        return str.toString();
+    }
+
+    /** Creates string for given indent level. */
+    @NonNull
+    private static String getIndent(int indentLevel) {
+        StringBuilder indentedString = new StringBuilder();
+        for (int i = 0; i < indentLevel; ++i) {
+            indentedString.append("  ");
+        }
+        return indentedString.toString();
     }
 
     /**
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetByDocumentIdRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetByDocumentIdRequest.java
index 58fd8be..e324b9f 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByDocumentIdRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByDocumentIdRequest.java
@@ -20,8 +20,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
-import com.android.internal.util.Preconditions;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -102,15 +100,11 @@
         return mTypePropertyPathsMap;
     }
 
-    /**
-     * Builder for {@link GetByDocumentIdRequest} objects.
-     *
-     * <p>Once {@link #build} is called, the instance can no longer be used.
-     */
+    /** Builder for {@link GetByDocumentIdRequest} objects. */
     public static final class Builder {
         private final String mNamespace;
-        private final Set<String> mIds = new ArraySet<>();
-        private final Map<String, List<String>> mProjectionTypePropertyPaths = new ArrayMap<>();
+        private ArraySet<String> mIds = new ArraySet<>();
+        private ArrayMap<String, List<String>> mProjectionTypePropertyPaths = new ArrayMap<>();
         private boolean mBuilt = false;
 
         /** Creates a {@link GetByDocumentIdRequest.Builder} instance. */
@@ -118,26 +112,19 @@
             mNamespace = Objects.requireNonNull(namespace);
         }
 
-        /**
-         * Adds one or more document IDs to the request.
-         *
-         * @throws IllegalStateException if the builder has already been used.
-         */
+        /** Adds one or more document IDs to the request. */
         @NonNull
         public Builder addIds(@NonNull String... ids) {
             Objects.requireNonNull(ids);
+            resetIfBuilt();
             return addIds(Arrays.asList(ids));
         }
 
-        /**
-         * Adds a collection of IDs to the request.
-         *
-         * @throws IllegalStateException if the builder has already been used.
-         */
+        /** Adds a collection of IDs to the request. */
         @NonNull
         public Builder addIds(@NonNull Collection<String> ids) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Objects.requireNonNull(ids);
+            resetIfBuilt();
             mIds.addAll(ids);
             return this;
         }
@@ -156,15 +143,14 @@
          * apply to all results, excepting any types that have their own, specific property paths
          * set.
          *
-         * @throws IllegalStateException if the builder has already been used.
          * @see SearchSpec.Builder#addProjection
          */
         @NonNull
         public Builder addProjection(
                 @NonNull String schemaType, @NonNull Collection<String> propertyPaths) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Objects.requireNonNull(schemaType);
             Objects.requireNonNull(propertyPaths);
+            resetIfBuilt();
             List<String> propertyPathsList = new ArrayList<>(propertyPaths.size());
             for (String propertyPath : propertyPaths) {
                 Objects.requireNonNull(propertyPath);
@@ -174,16 +160,23 @@
             return this;
         }
 
-        /**
-         * Builds a new {@link GetByDocumentIdRequest}.
-         *
-         * @throws IllegalStateException if the builder has already been used.
-         */
+        /** Builds a new {@link GetByDocumentIdRequest}. */
         @NonNull
         public GetByDocumentIdRequest build() {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             mBuilt = true;
-            return new GetByDocumentIdRequest(mNamespace, mIds, mProjectionTypePropertyPaths);
+            return new GetByDocumentIdRequest(
+                    mNamespace, new ArraySet<>(mIds), new ArrayMap<>(mProjectionTypePropertyPaths));
+        }
+
+        private void resetIfBuilt() {
+            if (mBuilt) {
+                mIds = new ArraySet<>(mIds);
+                // No need to clone each propertyPathsList inside mProjectionTypePropertyPaths since
+                // the builder only replaces it, never adds to it. So even if the builder is used
+                // again, the previous one will remain with the object.
+                mProjectionTypePropertyPaths = new ArrayMap<>(mProjectionTypePropertyPaths);
+                mBuilt = false;
+            }
         }
     }
 }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
index 6f8cbe8..8816c78 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
@@ -21,8 +21,6 @@
 import android.os.Bundle;
 import android.util.ArraySet;
 
-import com.android.internal.util.Preconditions;
-
 import java.util.ArrayList;
 import java.util.Objects;
 import java.util.Set;
@@ -76,8 +74,8 @@
     /** Builder for {@link GetSchemaResponse} objects. */
     public static final class Builder {
         private int mVersion = 0;
+        private ArrayList<Bundle> mSchemaBundles = new ArrayList<>();
         private boolean mBuilt = false;
-        private final ArrayList<Bundle> mSchemaBundles = new ArrayList<>();
 
         /**
          * Sets the database overall schema version.
@@ -86,6 +84,7 @@
          */
         @NonNull
         public Builder setVersion(@IntRange(from = 0) int version) {
+            resetIfBuilt();
             mVersion = version;
             return this;
         }
@@ -93,6 +92,8 @@
         /** Adds one {@link AppSearchSchema} to the schema list. */
         @NonNull
         public Builder addSchema(@NonNull AppSearchSchema schema) {
+            Objects.requireNonNull(schema);
+            resetIfBuilt();
             mSchemaBundles.add(schema.getBundle());
             return this;
         }
@@ -100,12 +101,18 @@
         /** Builds a {@link GetSchemaResponse} object. */
         @NonNull
         public GetSchemaResponse build() {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Bundle bundle = new Bundle();
             bundle.putInt(VERSION_FIELD, mVersion);
             bundle.putParcelableArrayList(SCHEMAS_FIELD, mSchemaBundles);
             mBuilt = true;
             return new GetSchemaResponse(bundle);
         }
+
+        private void resetIfBuilt() {
+            if (mBuilt) {
+                mSchemaBundles = new ArrayList<>(mSchemaBundles);
+                mBuilt = false;
+            }
+        }
     }
 }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
index b49e0e8..3424128 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
@@ -19,8 +19,6 @@
 
 import android.annotation.NonNull;
 
-import com.android.internal.util.Preconditions;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -46,50 +44,41 @@
         return Collections.unmodifiableList(mDocuments);
     }
 
-    /**
-     * Builder for {@link PutDocumentsRequest} objects.
-     *
-     * <p>Once {@link #build} is called, the instance can no longer be used.
-     */
+    /** Builder for {@link PutDocumentsRequest} objects. */
     public static final class Builder {
-        private final List<GenericDocument> mDocuments = new ArrayList<>();
+        private ArrayList<GenericDocument> mDocuments = new ArrayList<>();
         private boolean mBuilt = false;
 
-        /**
-         * Adds one or more {@link GenericDocument} objects to the request.
-         *
-         * @throws IllegalStateException if the builder has already been used.
-         */
+        /** Adds one or more {@link GenericDocument} objects to the request. */
         @NonNull
         public Builder addGenericDocuments(@NonNull GenericDocument... documents) {
             Objects.requireNonNull(documents);
+            resetIfBuilt();
             return addGenericDocuments(Arrays.asList(documents));
         }
 
-        /**
-         * Adds a collection of {@link GenericDocument} objects to the request.
-         *
-         * @throws IllegalStateException if the builder has already been used.
-         */
+        /** Adds a collection of {@link GenericDocument} objects to the request. */
         @NonNull
         public Builder addGenericDocuments(
                 @NonNull Collection<? extends GenericDocument> documents) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Objects.requireNonNull(documents);
+            resetIfBuilt();
             mDocuments.addAll(documents);
             return this;
         }
 
-        /**
-         * Creates a new {@link PutDocumentsRequest} object.
-         *
-         * @throws IllegalStateException if the builder has already been used.
-         */
+        /** Creates a new {@link PutDocumentsRequest} object. */
         @NonNull
         public PutDocumentsRequest build() {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             mBuilt = true;
             return new PutDocumentsRequest(mDocuments);
         }
+
+        private void resetIfBuilt() {
+            if (mBuilt) {
+                mDocuments = new ArrayList<>(mDocuments);
+                mBuilt = false;
+            }
+        }
     }
 }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByDocumentIdRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByDocumentIdRequest.java
index 1afbe27..b86fd27 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByDocumentIdRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByDocumentIdRequest.java
@@ -19,8 +19,6 @@
 import android.annotation.NonNull;
 import android.util.ArraySet;
 
-import com.android.internal.util.Preconditions;
-
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -54,14 +52,10 @@
         return Collections.unmodifiableSet(mIds);
     }
 
-    /**
-     * Builder for {@link RemoveByDocumentIdRequest} objects.
-     *
-     * <p>Once {@link #build} is called, the instance can no longer be used.
-     */
+    /** Builder for {@link RemoveByDocumentIdRequest} objects. */
     public static final class Builder {
         private final String mNamespace;
-        private final Set<String> mIds = new ArraySet<>();
+        private ArraySet<String> mIds = new ArraySet<>();
         private boolean mBuilt = false;
 
         /** Creates a {@link RemoveByDocumentIdRequest.Builder} instance. */
@@ -69,40 +63,35 @@
             mNamespace = Objects.requireNonNull(namespace);
         }
 
-        /**
-         * Adds one or more document IDs to the request.
-         *
-         * @throws IllegalStateException if the builder has already been used.
-         */
+        /** Adds one or more document IDs to the request. */
         @NonNull
         public Builder addIds(@NonNull String... ids) {
             Objects.requireNonNull(ids);
+            resetIfBuilt();
             return addIds(Arrays.asList(ids));
         }
 
-        /**
-         * Adds a collection of IDs to the request.
-         *
-         * @throws IllegalStateException if the builder has already been used.
-         */
+        /** Adds a collection of IDs to the request. */
         @NonNull
         public Builder addIds(@NonNull Collection<String> ids) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Objects.requireNonNull(ids);
+            resetIfBuilt();
             mIds.addAll(ids);
             return this;
         }
 
-        /**
-         * Builds a new {@link RemoveByDocumentIdRequest}.
-         *
-         * @throws IllegalStateException if the builder has already been used.
-         */
+        /** Builds a new {@link RemoveByDocumentIdRequest}. */
         @NonNull
         public RemoveByDocumentIdRequest build() {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             mBuilt = true;
             return new RemoveByDocumentIdRequest(mNamespace, mIds);
         }
+
+        private void resetIfBuilt() {
+            if (mBuilt) {
+                mIds = new ArraySet<>(mIds);
+                mBuilt = false;
+            }
+        }
     }
 }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java
index 3947ba7..b6575c2 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java
@@ -19,8 +19,6 @@
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.NonNull;
 
-import com.android.internal.util.Preconditions;
-
 import java.util.Objects;
 
 /**
@@ -94,7 +92,6 @@
         private final String mNamespace;
         private final String mDocumentId;
         private Long mUsageTimestampMillis;
-        private boolean mBuilt = false;
 
         /** Creates a {@link ReportSystemUsageRequest.Builder} instance. */
         public Builder(
@@ -116,29 +113,20 @@
          *
          * <p>If unset, this defaults to the current timestamp at the time that the {@link
          * ReportSystemUsageRequest} is constructed.
-         *
-         * @throws IllegalStateException if the builder has already been used
          */
         @NonNull
         public ReportSystemUsageRequest.Builder setUsageTimestampMillis(
                 @CurrentTimeMillisLong long usageTimestampMillis) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             mUsageTimestampMillis = usageTimestampMillis;
             return this;
         }
 
-        /**
-         * Builds a new {@link ReportSystemUsageRequest}.
-         *
-         * @throws IllegalStateException if the builder has already been used
-         */
+        /** Builds a new {@link ReportSystemUsageRequest}. */
         @NonNull
         public ReportSystemUsageRequest build() {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             if (mUsageTimestampMillis == null) {
                 mUsageTimestampMillis = System.currentTimeMillis();
             }
-            mBuilt = true;
             return new ReportSystemUsageRequest(
                     mPackageName, mDatabase, mNamespace, mDocumentId, mUsageTimestampMillis);
         }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
index 5cb59b3..59fa6e0 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
@@ -20,8 +20,6 @@
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 
-import com.android.internal.util.Preconditions;
-
 import java.util.Objects;
 
 /**
@@ -72,7 +70,6 @@
         // TODO(b/181887768): Make this final
         private String mDocumentId;
         private Long mUsageTimestampMillis;
-        private boolean mBuilt = false;
 
         /** Creates a {@link ReportUsageRequest.Builder} instance. */
         public Builder(@NonNull String namespace, @NonNull String documentId) {
@@ -122,29 +119,20 @@
          *
          * <p>If unset, this defaults to the current timestamp at the time that the {@link
          * ReportUsageRequest} is constructed.
-         *
-         * @throws IllegalStateException if the builder has already been used
          */
         @NonNull
         public ReportUsageRequest.Builder setUsageTimestampMillis(
                 @CurrentTimeMillisLong long usageTimestampMillis) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             mUsageTimestampMillis = usageTimestampMillis;
             return this;
         }
 
-        /**
-         * Builds a new {@link ReportUsageRequest}.
-         *
-         * @throws IllegalStateException if the builder has already been used
-         */
+        /** Builds a new {@link ReportUsageRequest}. */
         @NonNull
         public ReportUsageRequest build() {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             if (mUsageTimestampMillis == null) {
                 mUsageTimestampMillis = System.currentTimeMillis();
             }
-            mBuilt = true;
             return new ReportUsageRequest(mNamespace, mDocumentId, mUsageTimestampMillis);
         }
     }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
index 9a1796c..4beb667 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Bundle;
 
 import com.android.internal.util.Preconditions;
@@ -82,8 +83,12 @@
         return mDocument;
     }
 
-    /** @deprecated This method exists only for dogfooder transition and must be removed. */
+    /**
+     * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+     * @hide
+     */
     @Deprecated
+    @UnsupportedAppUsage
     @NonNull
     public List<MatchInfo> getMatches() {
         return getMatchInfos();
@@ -164,10 +169,12 @@
 
     /** Builder for {@link SearchResult} objects. */
     public static final class Builder {
-        private final Bundle mBundle = new Bundle();
-        private final ArrayList<Bundle> mMatchInfos = new ArrayList<>();
-
-        private boolean mBuilt;
+        private final String mPackageName;
+        private final String mDatabaseName;
+        private ArrayList<Bundle> mMatchInfoBundles = new ArrayList<>();
+        private GenericDocument mGenericDocument;
+        private double mRankingSignal;
+        private boolean mBuilt = false;
 
         /**
          * Constructs a new builder for {@link SearchResult} objects.
@@ -176,24 +183,25 @@
          * @param databaseName the database name the matched document belongs to.
          */
         public Builder(@NonNull String packageName, @NonNull String databaseName) {
-            mBundle.putString(PACKAGE_NAME_FIELD, Objects.requireNonNull(packageName));
-            mBundle.putString(DATABASE_NAME_FIELD, Objects.requireNonNull(databaseName));
+            mPackageName = Objects.requireNonNull(packageName);
+            mDatabaseName = Objects.requireNonNull(databaseName);
         }
 
-        /**
-         * Sets the document which matched.
-         *
-         * @throws IllegalStateException if the builder has already been used
-         */
+        /** Sets the document which matched. */
         @NonNull
         public Builder setGenericDocument(@NonNull GenericDocument document) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mBundle.putBundle(DOCUMENT_FIELD, document.getBundle());
+            Objects.requireNonNull(document);
+            resetIfBuilt();
+            mGenericDocument = document;
             return this;
         }
 
-        /** @deprecated This method exists only for dogfooder transition and must be removed. */
+        /**
+         * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+         * @hide
+         */
         @Deprecated
+        @UnsupportedAppUsage
         @NonNull
         public Builder addMatch(@NonNull MatchInfo matchInfo) {
             return addMatchInfo(matchInfo);
@@ -202,34 +210,41 @@
         /** Adds another match to this SearchResult. */
         @NonNull
         public Builder addMatchInfo(@NonNull MatchInfo matchInfo) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkState(
                     matchInfo.mDocument == null,
                     "This MatchInfo is already associated with a SearchResult and can't be "
                             + "reassigned");
-            mMatchInfos.add(matchInfo.mBundle);
+            resetIfBuilt();
+            mMatchInfoBundles.add(matchInfo.mBundle);
             return this;
         }
 
         /** Sets the ranking signal of the matched document in this SearchResult. */
         @NonNull
         public Builder setRankingSignal(double rankingSignal) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mBundle.putDouble(RANKING_SIGNAL_FIELD, rankingSignal);
+            resetIfBuilt();
+            mRankingSignal = rankingSignal;
             return this;
         }
 
-        /**
-         * Constructs a new {@link SearchResult}.
-         *
-         * @throws IllegalStateException if the builder has already been used
-         */
+        /** Constructs a new {@link SearchResult}. */
         @NonNull
         public SearchResult build() {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mBundle.putParcelableArrayList(MATCH_INFOS_FIELD, mMatchInfos);
+            Bundle bundle = new Bundle();
+            bundle.putString(PACKAGE_NAME_FIELD, mPackageName);
+            bundle.putString(DATABASE_NAME_FIELD, mDatabaseName);
+            bundle.putBundle(DOCUMENT_FIELD, mGenericDocument.getBundle());
+            bundle.putDouble(RANKING_SIGNAL_FIELD, mRankingSignal);
+            bundle.putParcelableArrayList(MATCH_INFOS_FIELD, mMatchInfoBundles);
             mBuilt = true;
-            return new SearchResult(mBundle);
+            return new SearchResult(bundle);
+        }
+
+        private void resetIfBuilt() {
+            if (mBuilt) {
+                mMatchInfoBundles = new ArrayList<>(mMatchInfoBundles);
+                mBuilt = false;
+            }
         }
     }
 
@@ -441,8 +456,9 @@
 
         /** Builder for {@link MatchInfo} objects. */
         public static final class Builder {
-            private final Bundle mBundle = new Bundle();
-            private boolean mBuilt = false;
+            private final String mPropertyPath;
+            private MatchRange mExactMatchRange = new MatchRange(0, 0);
+            private MatchRange mSnippetRange = new MatchRange(0, 0);
 
             /**
              * Creates a new {@link MatchInfo.Builder} reporting a match with the given property
@@ -458,49 +474,33 @@
              *                     which property in the document these snippets correspond to.
              */
             public Builder(@NonNull String propertyPath) {
-                mBundle.putString(
-                        SearchResult.MatchInfo.PROPERTY_PATH_FIELD,
-                        Objects.requireNonNull(propertyPath));
+                mPropertyPath = Objects.requireNonNull(propertyPath);
             }
 
-            /**
-             * Sets the exact {@link MatchRange} corresponding to the given entry.
-             *
-             * @throws IllegalStateException if the builder has already been used
-             */
+            /** Sets the exact {@link MatchRange} corresponding to the given entry. */
             @NonNull
             public Builder setExactMatchRange(@NonNull MatchRange matchRange) {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
-                Objects.requireNonNull(matchRange);
-                mBundle.putInt(MatchInfo.EXACT_MATCH_RANGE_LOWER_FIELD, matchRange.getStart());
-                mBundle.putInt(MatchInfo.EXACT_MATCH_RANGE_UPPER_FIELD, matchRange.getEnd());
+                mExactMatchRange = Objects.requireNonNull(matchRange);
                 return this;
             }
 
-            /**
-             * Sets the snippet {@link MatchRange} corresponding to the given entry.
-             *
-             * @throws IllegalStateException if the builder has already been used
-             */
+            /** Sets the snippet {@link MatchRange} corresponding to the given entry. */
             @NonNull
             public Builder setSnippetRange(@NonNull MatchRange matchRange) {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
-                Objects.requireNonNull(matchRange);
-                mBundle.putInt(MatchInfo.SNIPPET_RANGE_LOWER_FIELD, matchRange.getStart());
-                mBundle.putInt(MatchInfo.SNIPPET_RANGE_UPPER_FIELD, matchRange.getEnd());
+                mSnippetRange = Objects.requireNonNull(matchRange);
                 return this;
             }
 
-            /**
-             * Constructs a new {@link MatchInfo}.
-             *
-             * @throws IllegalStateException if the builder has already been used
-             */
+            /** Constructs a new {@link MatchInfo}. */
             @NonNull
             public MatchInfo build() {
-                Preconditions.checkState(!mBuilt, "Builder has already been used");
-                mBuilt = true;
-                return new MatchInfo(mBundle, /*document=*/ null);
+                Bundle bundle = new Bundle();
+                bundle.putString(SearchResult.MatchInfo.PROPERTY_PATH_FIELD, mPropertyPath);
+                bundle.putInt(MatchInfo.EXACT_MATCH_RANGE_LOWER_FIELD, mExactMatchRange.getStart());
+                bundle.putInt(MatchInfo.EXACT_MATCH_RANGE_UPPER_FIELD, mExactMatchRange.getEnd());
+                bundle.putInt(MatchInfo.SNIPPET_RANGE_LOWER_FIELD, mSnippetRange.getStart());
+                bundle.putInt(MatchInfo.SNIPPET_RANGE_UPPER_FIELD, mSnippetRange.getEnd());
+                return new MatchInfo(bundle, /*document=*/ null);
             }
         }
     }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
index 1c57c75..5abd4f6 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
@@ -20,6 +20,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
+import android.app.appsearch.util.BundleUtil;
 import android.os.Bundle;
 import android.util.ArrayMap;
 
@@ -310,22 +311,22 @@
 
     /** Builder for {@link SearchSpec objects}. */
     public static final class Builder {
+        private ArrayList<String> mSchemas = new ArrayList<>();
+        private ArrayList<String> mNamespaces = new ArrayList<>();
+        private ArrayList<String> mPackageNames = new ArrayList<>();
+        private Bundle mProjectionTypePropertyMasks = new Bundle();
 
-        private final Bundle mBundle;
-        private final ArrayList<String> mSchemas = new ArrayList<>();
-        private final ArrayList<String> mNamespaces = new ArrayList<>();
-        private final ArrayList<String> mPackageNames = new ArrayList<>();
-        private final Bundle mProjectionTypePropertyMasks = new Bundle();
+        private int mResultCountPerPage = DEFAULT_NUM_PER_PAGE;
+        private @TermMatch int mTermMatchType = TERM_MATCH_PREFIX;
+        private int mSnippetCount = 0;
+        private int mSnippetCountPerProperty = MAX_SNIPPET_PER_PROPERTY_COUNT;
+        private int mMaxSnippetSize = 0;
+        private @RankingStrategy int mRankingStrategy = RANKING_STRATEGY_NONE;
+        private @Order int mOrder = ORDER_DESCENDING;
+        private @GroupingType int mGroupingTypeFlags = 0;
+        private int mGroupingLimit = 0;
         private boolean mBuilt = false;
 
-        /** Creates a new {@link SearchSpec.Builder}. */
-        public Builder() {
-            mBundle = new Bundle();
-            mBundle.putInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
-            mBundle.putInt(TERM_MATCH_TYPE_FIELD, TERM_MATCH_PREFIX);
-            mBundle.putInt(SNIPPET_COUNT_PER_PROPERTY_FIELD, MAX_SNIPPET_PER_PROPERTY_COUNT);
-        }
-
         /**
          * Indicates how the query terms should match {@code TermMatchCode} in the index.
          *
@@ -333,11 +334,11 @@
          * SearchSpec#TERM_MATCH_PREFIX}.
          */
         @NonNull
-        public Builder setTermMatch(@TermMatch int termMatchTypeCode) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
+        public Builder setTermMatch(@TermMatch int termMatchType) {
             Preconditions.checkArgumentInRange(
-                    termMatchTypeCode, TERM_MATCH_EXACT_ONLY, TERM_MATCH_PREFIX, "Term match type");
-            mBundle.putInt(TERM_MATCH_TYPE_FIELD, termMatchTypeCode);
+                    termMatchType, TERM_MATCH_EXACT_ONLY, TERM_MATCH_PREFIX, "Term match type");
+            resetIfBuilt();
+            mTermMatchType = termMatchType;
             return this;
         }
 
@@ -350,7 +351,7 @@
         @NonNull
         public Builder addFilterSchemas(@NonNull String... schemas) {
             Objects.requireNonNull(schemas);
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            resetIfBuilt();
             return addFilterSchemas(Arrays.asList(schemas));
         }
 
@@ -363,7 +364,7 @@
         @NonNull
         public Builder addFilterSchemas(@NonNull Collection<String> schemas) {
             Objects.requireNonNull(schemas);
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            resetIfBuilt();
             mSchemas.addAll(schemas);
             return this;
         }
@@ -377,7 +378,7 @@
         @NonNull
         public Builder addFilterNamespaces(@NonNull String... namespaces) {
             Objects.requireNonNull(namespaces);
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            resetIfBuilt();
             return addFilterNamespaces(Arrays.asList(namespaces));
         }
 
@@ -390,7 +391,7 @@
         @NonNull
         public Builder addFilterNamespaces(@NonNull Collection<String> namespaces) {
             Objects.requireNonNull(namespaces);
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            resetIfBuilt();
             mNamespaces.addAll(namespaces);
             return this;
         }
@@ -406,7 +407,7 @@
         @NonNull
         public Builder addFilterPackageNames(@NonNull String... packageNames) {
             Objects.requireNonNull(packageNames);
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            resetIfBuilt();
             return addFilterPackageNames(Arrays.asList(packageNames));
         }
 
@@ -421,7 +422,7 @@
         @NonNull
         public Builder addFilterPackageNames(@NonNull Collection<String> packageNames) {
             Objects.requireNonNull(packageNames);
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            resetIfBuilt();
             mPackageNames.addAll(packageNames);
             return this;
         }
@@ -433,23 +434,24 @@
          */
         @NonNull
         public SearchSpec.Builder setResultCountPerPage(
-                @IntRange(from = 0, to = MAX_NUM_PER_PAGE) int numPerPage) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkArgumentInRange(numPerPage, 0, MAX_NUM_PER_PAGE, "NumPerPage");
-            mBundle.putInt(NUM_PER_PAGE_FIELD, numPerPage);
+                @IntRange(from = 0, to = MAX_NUM_PER_PAGE) int resultCountPerPage) {
+            Preconditions.checkArgumentInRange(
+                    resultCountPerPage, 0, MAX_NUM_PER_PAGE, "resultCountPerPage");
+            resetIfBuilt();
+            mResultCountPerPage = resultCountPerPage;
             return this;
         }
 
         /** Sets ranking strategy for AppSearch results. */
         @NonNull
         public Builder setRankingStrategy(@RankingStrategy int rankingStrategy) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkArgumentInRange(
                     rankingStrategy,
                     RANKING_STRATEGY_NONE,
                     RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP,
                     "Result ranking strategy");
-            mBundle.putInt(RANKING_STRATEGY_FIELD, rankingStrategy);
+            resetIfBuilt();
+            mRankingStrategy = rankingStrategy;
             return this;
         }
 
@@ -461,10 +463,10 @@
          */
         @NonNull
         public Builder setOrder(@Order int order) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkArgumentInRange(
                     order, ORDER_DESCENDING, ORDER_ASCENDING, "Result ranking order");
-            mBundle.putInt(ORDER_FIELD, order);
+            resetIfBuilt();
+            mOrder = order;
             return this;
         }
 
@@ -481,9 +483,9 @@
         @NonNull
         public SearchSpec.Builder setSnippetCount(
                 @IntRange(from = 0, to = MAX_SNIPPET_COUNT) int snippetCount) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkArgumentInRange(snippetCount, 0, MAX_SNIPPET_COUNT, "snippetCount");
-            mBundle.putInt(SNIPPET_COUNT_FIELD, snippetCount);
+            resetIfBuilt();
+            mSnippetCount = snippetCount;
             return this;
         }
 
@@ -502,13 +504,13 @@
         public SearchSpec.Builder setSnippetCountPerProperty(
                 @IntRange(from = 0, to = MAX_SNIPPET_PER_PROPERTY_COUNT)
                         int snippetCountPerProperty) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkArgumentInRange(
                     snippetCountPerProperty,
                     0,
                     MAX_SNIPPET_PER_PROPERTY_COUNT,
                     "snippetCountPerProperty");
-            mBundle.putInt(SNIPPET_COUNT_PER_PROPERTY_FIELD, snippetCountPerProperty);
+            resetIfBuilt();
+            mSnippetCountPerProperty = snippetCountPerProperty;
             return this;
         }
 
@@ -527,10 +529,10 @@
         @NonNull
         public SearchSpec.Builder setMaxSnippetSize(
                 @IntRange(from = 0, to = MAX_SNIPPET_SIZE_LIMIT) int maxSnippetSize) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkArgumentInRange(
                     maxSnippetSize, 0, MAX_SNIPPET_SIZE_LIMIT, "maxSnippetSize");
-            mBundle.putInt(MAX_SNIPPET_FIELD, maxSnippetSize);
+            resetIfBuilt();
+            mMaxSnippetSize = maxSnippetSize;
             return this;
         }
 
@@ -599,9 +601,9 @@
         @NonNull
         public SearchSpec.Builder addProjection(
                 @NonNull String schema, @NonNull Collection<String> propertyPaths) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Objects.requireNonNull(schema);
             Objects.requireNonNull(propertyPaths);
+            resetIfBuilt();
             ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
             for (String propertyPath : propertyPaths) {
                 Objects.requireNonNull(propertyPath);
@@ -634,25 +636,41 @@
         public Builder setResultGrouping(@GroupingType int groupingTypeFlags, int limit) {
             Preconditions.checkState(
                     groupingTypeFlags != 0, "Result grouping type cannot be zero.");
-            mBundle.putInt(RESULT_GROUPING_TYPE_FLAGS, groupingTypeFlags);
-            mBundle.putInt(RESULT_GROUPING_LIMIT, limit);
+            resetIfBuilt();
+            mGroupingTypeFlags = groupingTypeFlags;
+            mGroupingLimit = limit;
             return this;
         }
 
-        /**
-         * Constructs a new {@link SearchSpec} from the contents of this builder.
-         *
-         * <p>After calling this method, the builder must no longer be used.
-         */
+        /** Constructs a new {@link SearchSpec} from the contents of this builder. */
         @NonNull
         public SearchSpec build() {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mBundle.putStringArrayList(NAMESPACE_FIELD, mNamespaces);
-            mBundle.putStringArrayList(SCHEMA_FIELD, mSchemas);
-            mBundle.putStringArrayList(PACKAGE_NAME_FIELD, mPackageNames);
-            mBundle.putBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD, mProjectionTypePropertyMasks);
+            Bundle bundle = new Bundle();
+            bundle.putStringArrayList(SCHEMA_FIELD, mSchemas);
+            bundle.putStringArrayList(NAMESPACE_FIELD, mNamespaces);
+            bundle.putStringArrayList(PACKAGE_NAME_FIELD, mPackageNames);
+            bundle.putBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD, mProjectionTypePropertyMasks);
+            bundle.putInt(NUM_PER_PAGE_FIELD, mResultCountPerPage);
+            bundle.putInt(TERM_MATCH_TYPE_FIELD, mTermMatchType);
+            bundle.putInt(SNIPPET_COUNT_FIELD, mSnippetCount);
+            bundle.putInt(SNIPPET_COUNT_PER_PROPERTY_FIELD, mSnippetCountPerProperty);
+            bundle.putInt(MAX_SNIPPET_FIELD, mMaxSnippetSize);
+            bundle.putInt(RANKING_STRATEGY_FIELD, mRankingStrategy);
+            bundle.putInt(ORDER_FIELD, mOrder);
+            bundle.putInt(RESULT_GROUPING_TYPE_FLAGS, mGroupingTypeFlags);
+            bundle.putInt(RESULT_GROUPING_LIMIT, mGroupingLimit);
             mBuilt = true;
-            return new SearchSpec(mBundle);
+            return new SearchSpec(bundle);
+        }
+
+        private void resetIfBuilt() {
+            if (mBuilt) {
+                mSchemas = new ArrayList<>(mSchemas);
+                mNamespaces = new ArrayList<>(mNamespaces);
+                mPackageNames = new ArrayList<>(mPackageNames);
+                mProjectionTypePropertyMasks = BundleUtil.deepCopy(mProjectionTypePropertyMasks);
+                mBuilt = false;
+            }
         }
     }
 }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
index 275b2c3..96002c5 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
@@ -167,17 +167,13 @@
         return mVersion;
     }
 
-    /**
-     * Builder for {@link SetSchemaRequest} objects.
-     *
-     * <p>Once {@link #build} is called, the instance can no longer be used.
-     */
+    /** Builder for {@link SetSchemaRequest} objects. */
     public static final class Builder {
-        private final Set<AppSearchSchema> mSchemas = new ArraySet<>();
-        private final Set<String> mSchemasNotDisplayedBySystem = new ArraySet<>();
-        private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages =
+        private ArraySet<AppSearchSchema> mSchemas = new ArraySet<>();
+        private ArraySet<String> mSchemasNotDisplayedBySystem = new ArraySet<>();
+        private ArrayMap<String, Set<PackageIdentifier>> mSchemasVisibleToPackages =
                 new ArrayMap<>();
-        private final Map<String, Migrator> mMigrators = new ArrayMap<>();
+        private ArrayMap<String, Migrator> mMigrators = new ArrayMap<>();
         private boolean mForceOverride = false;
         private int mVersion = 1;
         private boolean mBuilt = false;
@@ -188,12 +184,11 @@
          * <p>An {@link AppSearchSchema} object represents one type of structured data.
          *
          * <p>Any documents of these types will be displayed on system UI surfaces by default.
-         *
-         * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public Builder addSchemas(@NonNull AppSearchSchema... schemas) {
             Objects.requireNonNull(schemas);
+            resetIfBuilt();
             return addSchemas(Arrays.asList(schemas));
         }
 
@@ -201,13 +196,11 @@
          * Adds a collection of {@link AppSearchSchema} objects to the schema.
          *
          * <p>An {@link AppSearchSchema} object represents one type of structured data.
-         *
-         * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public Builder addSchemas(@NonNull Collection<AppSearchSchema> schemas) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Objects.requireNonNull(schemas);
+            resetIfBuilt();
             mSchemas.addAll(schemas);
             return this;
         }
@@ -225,7 +218,6 @@
          * @param schemaType The name of an {@link AppSearchSchema} within the same {@link
          *     SetSchemaRequest}, which will be configured.
          * @param displayed Whether documents of this type will be displayed on system UI surfaces.
-         * @throws IllegalStateException if the builder has already been used.
          */
         // Merged list available from getSchemasNotDisplayedBySystem
         @SuppressLint("MissingGetterMatchingBuilder")
@@ -233,8 +225,7 @@
         public Builder setSchemaTypeDisplayedBySystem(
                 @NonNull String schemaType, boolean displayed) {
             Objects.requireNonNull(schemaType);
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-
+            resetIfBuilt();
             if (displayed) {
                 mSchemasNotDisplayedBySystem.remove(schemaType);
             } else {
@@ -262,7 +253,6 @@
          * @param schemaType The schema type to set visibility on.
          * @param visible Whether the {@code schemaType} will be visible or not.
          * @param packageIdentifier Represents the package that will be granted visibility.
-         * @throws IllegalStateException if the builder has already been used.
          */
         // Merged list available from getSchemasVisibleToPackages
         @SuppressLint("MissingGetterMatchingBuilder")
@@ -273,7 +263,7 @@
                 @NonNull PackageIdentifier packageIdentifier) {
             Objects.requireNonNull(schemaType);
             Objects.requireNonNull(packageIdentifier);
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            resetIfBuilt();
 
             Set<PackageIdentifier> packageIdentifiers = mSchemasVisibleToPackages.get(schemaType);
             if (visible) {
@@ -324,6 +314,7 @@
         public Builder setMigrator(@NonNull String schemaType, @NonNull Migrator migrator) {
             Objects.requireNonNull(schemaType);
             Objects.requireNonNull(migrator);
+            resetIfBuilt();
             mMigrators.put(schemaType, migrator);
             return this;
         }
@@ -352,6 +343,7 @@
         @NonNull
         public Builder setMigrators(@NonNull Map<String, Migrator> migrators) {
             Objects.requireNonNull(migrators);
+            resetIfBuilt();
             mMigrators.putAll(migrators);
             return this;
         }
@@ -369,6 +361,7 @@
          */
         @NonNull
         public Builder setForceOverride(boolean forceOverride) {
+            resetIfBuilt();
             mForceOverride = forceOverride;
             return this;
         }
@@ -391,8 +384,7 @@
          * @param version A positive integer representing the version of the entire set of schemas
          *     represents the version of the whole schema in the {@link AppSearchSession} database,
          *     default version is 1.
-         * @throws IllegalStateException if the version is negative or the builder has already been
-         *     used.
+         * @throws IllegalArgumentException if the version is negative.
          * @see AppSearchSession#setSchema
          * @see Migrator
          * @see SetSchemaRequest.Builder#setMigrator
@@ -400,6 +392,7 @@
         @NonNull
         public Builder setVersion(@IntRange(from = 1) int version) {
             Preconditions.checkArgument(version >= 1, "Version must be a positive number.");
+            resetIfBuilt();
             mVersion = version;
             return this;
         }
@@ -409,12 +402,9 @@
          *
          * @throws IllegalArgumentException if schema types were referenced, but the corresponding
          *     {@link AppSearchSchema} type was never added.
-         * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public SetSchemaRequest build() {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-
             // Verify that any schema types with display or visibility settings refer to a real
             // schema.
             // Create a copy because we're going to remove from the set for verification purposes.
@@ -440,5 +430,22 @@
                     mForceOverride,
                     mVersion);
         }
+
+        private void resetIfBuilt() {
+            if (mBuilt) {
+                ArrayMap<String, Set<PackageIdentifier>> schemasVisibleToPackages =
+                        new ArrayMap<>(mSchemasVisibleToPackages.size());
+                for (Map.Entry<String, Set<PackageIdentifier>> entry :
+                        mSchemasVisibleToPackages.entrySet()) {
+                    schemasVisibleToPackages.put(entry.getKey(), new ArraySet<>(entry.getValue()));
+                }
+                mSchemasVisibleToPackages = schemasVisibleToPackages;
+
+                mSchemas = new ArraySet<>(mSchemas);
+                mSchemasNotDisplayedBySystem = new ArraySet<>(mSchemasNotDisplayedBySystem);
+                mMigrators = new ArrayMap<>(mMigrators);
+                mBuilt = false;
+            }
+        }
     }
 }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
index 7ad5fe8..2c75b71 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -179,81 +179,88 @@
 
     /** Builder for {@link SetSchemaResponse} objects. */
     public static final class Builder {
-        private final ArrayList<MigrationFailure> mMigrationFailures = new ArrayList<>();
-        private final ArrayList<String> mDeletedTypes = new ArrayList<>();
-        private final ArrayList<String> mMigratedTypes = new ArrayList<>();
-        private final ArrayList<String> mIncompatibleTypes = new ArrayList<>();
+        private List<MigrationFailure> mMigrationFailures = new ArrayList<>();
+        private ArrayList<String> mDeletedTypes = new ArrayList<>();
+        private ArrayList<String> mMigratedTypes = new ArrayList<>();
+        private ArrayList<String> mIncompatibleTypes = new ArrayList<>();
         private boolean mBuilt = false;
 
         /** Adds {@link MigrationFailure}s to the list of migration failures. */
         @NonNull
         public Builder addMigrationFailures(
                 @NonNull Collection<MigrationFailure> migrationFailures) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mMigrationFailures.addAll(Objects.requireNonNull(migrationFailures));
+            Objects.requireNonNull(migrationFailures);
+            resetIfBuilt();
+            mMigrationFailures.addAll(migrationFailures);
             return this;
         }
 
         /** Adds a {@link MigrationFailure} to the list of migration failures. */
         @NonNull
         public Builder addMigrationFailure(@NonNull MigrationFailure migrationFailure) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mMigrationFailures.add(Objects.requireNonNull(migrationFailure));
+            Objects.requireNonNull(migrationFailure);
+            resetIfBuilt();
+            mMigrationFailures.add(migrationFailure);
             return this;
         }
 
         /** Adds deletedTypes to the list of deleted schema types. */
         @NonNull
         public Builder addDeletedTypes(@NonNull Collection<String> deletedTypes) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mDeletedTypes.addAll(Objects.requireNonNull(deletedTypes));
+            Objects.requireNonNull(deletedTypes);
+            resetIfBuilt();
+            mDeletedTypes.addAll(deletedTypes);
             return this;
         }
 
         /** Adds one deletedType to the list of deleted schema types. */
         @NonNull
         public Builder addDeletedType(@NonNull String deletedType) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mDeletedTypes.add(Objects.requireNonNull(deletedType));
+            Objects.requireNonNull(deletedType);
+            resetIfBuilt();
+            mDeletedTypes.add(deletedType);
             return this;
         }
 
         /** Adds incompatibleTypes to the list of incompatible schema types. */
         @NonNull
         public Builder addIncompatibleTypes(@NonNull Collection<String> incompatibleTypes) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mIncompatibleTypes.addAll(Objects.requireNonNull(incompatibleTypes));
+            Objects.requireNonNull(incompatibleTypes);
+            resetIfBuilt();
+            mIncompatibleTypes.addAll(incompatibleTypes);
             return this;
         }
 
         /** Adds one incompatibleType to the list of incompatible schema types. */
         @NonNull
         public Builder addIncompatibleType(@NonNull String incompatibleType) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mIncompatibleTypes.add(Objects.requireNonNull(incompatibleType));
+            Objects.requireNonNull(incompatibleType);
+            resetIfBuilt();
+            mIncompatibleTypes.add(incompatibleType);
             return this;
         }
 
         /** Adds migratedTypes to the list of migrated schema types. */
         @NonNull
         public Builder addMigratedTypes(@NonNull Collection<String> migratedTypes) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mMigratedTypes.addAll(Objects.requireNonNull(migratedTypes));
+            Objects.requireNonNull(migratedTypes);
+            resetIfBuilt();
+            mMigratedTypes.addAll(migratedTypes);
             return this;
         }
 
         /** Adds one migratedType to the list of migrated schema types. */
         @NonNull
         public Builder addMigratedType(@NonNull String migratedType) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mMigratedTypes.add(Objects.requireNonNull(migratedType));
+            Objects.requireNonNull(migratedType);
+            resetIfBuilt();
+            mMigratedTypes.add(migratedType);
             return this;
         }
 
         /** Builds a {@link SetSchemaResponse} object. */
         @NonNull
         public SetSchemaResponse build() {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
             Bundle bundle = new Bundle();
             bundle.putStringArrayList(INCOMPATIBLE_TYPES_FIELD, mIncompatibleTypes);
             bundle.putStringArrayList(DELETED_TYPES_FIELD, mDeletedTypes);
@@ -264,6 +271,16 @@
             // AppSearchSession after we pass SetSchemaResponse via binder.
             return new SetSchemaResponse(bundle, mMigrationFailures);
         }
+
+        private void resetIfBuilt() {
+            if (mBuilt) {
+                mMigrationFailures = new ArrayList<>(mMigrationFailures);
+                mDeletedTypes = new ArrayList<>(mDeletedTypes);
+                mMigratedTypes = new ArrayList<>(mMigratedTypes);
+                mIncompatibleTypes = new ArrayList<>(mIncompatibleTypes);
+                mBuilt = false;
+            }
+        }
     }
 
     /**
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java b/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java
index 502b939..64d4828 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java
@@ -19,8 +19,6 @@
 import android.annotation.NonNull;
 import android.os.Bundle;
 
-import com.android.internal.util.Preconditions;
-
 import java.util.Objects;
 
 /** The response class of {@code AppSearchSession#getStorageInfo}. */
@@ -74,39 +72,39 @@
 
     /** Builder for {@link StorageInfo} objects. */
     public static final class Builder {
-        private final Bundle mBundle = new Bundle();
-        private boolean mBuilt = false;
+        private long mSizeBytes;
+        private int mAliveDocumentsCount;
+        private int mAliveNamespacesCount;
 
         /** Sets the size in bytes. */
         @NonNull
         public StorageInfo.Builder setSizeBytes(long sizeBytes) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mBundle.putLong(SIZE_BYTES_FIELD, sizeBytes);
+            mSizeBytes = sizeBytes;
             return this;
         }
 
         /** Sets the number of alive documents. */
         @NonNull
-        public StorageInfo.Builder setAliveDocumentsCount(int numAliveDocuments) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mBundle.putInt(ALIVE_DOCUMENTS_COUNT, numAliveDocuments);
+        public StorageInfo.Builder setAliveDocumentsCount(int aliveDocumentsCount) {
+            mAliveDocumentsCount = aliveDocumentsCount;
             return this;
         }
 
         /** Sets the number of alive namespaces. */
         @NonNull
-        public StorageInfo.Builder setAliveNamespacesCount(int numAliveNamespaces) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mBundle.putInt(ALIVE_NAMESPACES_COUNT, numAliveNamespaces);
+        public StorageInfo.Builder setAliveNamespacesCount(int aliveNamespacesCount) {
+            mAliveNamespacesCount = aliveNamespacesCount;
             return this;
         }
 
         /** Builds a {@link StorageInfo} object. */
         @NonNull
         public StorageInfo build() {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mBuilt = true;
-            return new StorageInfo(mBundle);
+            Bundle bundle = new Bundle();
+            bundle.putLong(SIZE_BYTES_FIELD, mSizeBytes);
+            bundle.putInt(ALIVE_DOCUMENTS_COUNT, mAliveDocumentsCount);
+            bundle.putInt(ALIVE_NAMESPACES_COUNT, mAliveNamespacesCount);
+            return new StorageInfo(bundle);
         }
     }
 }
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 a4188a2..0f42d85 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -29,14 +29,16 @@
 import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.GetSchemaResponse;
-import android.app.appsearch.IAppSearchBatchResultCallback;
-import android.app.appsearch.IAppSearchManager;
-import android.app.appsearch.IAppSearchResultCallback;
 import android.app.appsearch.PackageIdentifier;
 import android.app.appsearch.SearchResultPage;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaResponse;
 import android.app.appsearch.StorageInfo;
+import android.app.appsearch.aidl.AppSearchBatchResultParcel;
+import android.app.appsearch.aidl.AppSearchResultParcel;
+import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
+import android.app.appsearch.aidl.IAppSearchManager;
+import android.app.appsearch.aidl.IAppSearchResultCallback;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -869,7 +871,7 @@
         private void invokeCallbackOnResult(
                 IAppSearchResultCallback callback, AppSearchResult<?> result) {
             try {
-                callback.onResult(result);
+                callback.onResult(new AppSearchResultParcel<>(result));
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to send result to the callback", e);
             }
@@ -877,9 +879,9 @@
 
         /** Invokes the {@link IAppSearchBatchResultCallback} with the result. */
         private void invokeCallbackOnResult(
-                IAppSearchBatchResultCallback callback, AppSearchBatchResult<?, ?> result) {
+                IAppSearchBatchResultCallback callback, AppSearchBatchResult<String, ?> result) {
             try {
-                callback.onResult(result);
+                callback.onResult(new AppSearchBatchResultParcel<>(result));
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to send result to the callback", e);
             }
@@ -891,8 +893,9 @@
          * <p>The throwable is convert to a {@link AppSearchResult};
          */
         private void invokeCallbackOnError(IAppSearchResultCallback callback, Throwable throwable) {
+            AppSearchResult<?> result = throwableToFailedResult(throwable);
             try {
-                callback.onResult(throwableToFailedResult(throwable));
+                callback.onResult(new AppSearchResultParcel<>(result));
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to send result to the callback", e);
             }
@@ -905,8 +908,9 @@
          */
         private void invokeCallbackOnError(
                 @NonNull IAppSearchBatchResultCallback callback, @NonNull Throwable throwable) {
+            AppSearchResult<?> result = throwableToFailedResult(throwable);
             try {
-                callback.onSystemError(throwableToFailedResult(throwable));
+                callback.onSystemError(new AppSearchResultParcel<>(result));
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to send error to the callback", e);
             }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/SetSchemaStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/SetSchemaStats.java
new file mode 100644
index 0000000..56a546a
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/SetSchemaStats.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2021 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.appsearch.external.localstorage.stats;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+
+import java.util.Objects;
+
+/**
+ * Class holds detailed stats for {@link
+ * android.app.appsearch.AppSearchSession#setSchema(SetSchemaRequest)}.
+ *
+ * @hide
+ */
+public final class SetSchemaStats {
+    @NonNull private final String mPackageName;
+
+    @NonNull private final String mDatabase;
+
+    /**
+     * The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal
+     * state.
+     */
+    @AppSearchResult.ResultCode private final int mStatusCode;
+
+    private final int mTotalLatencyMillis;
+
+    /** Overall time used for the native function call. */
+    private final int mNativeLatencyMillis;
+
+    /** Number of newly added schema types. */
+    private final int mNewTypeCount;
+
+    /** Number of deleted schema types. */
+    private final int mDeletedTypeCount;
+
+    /** Number of compatible schema type changes. */
+    private final int mCompatibleTypeChangeCount;
+
+    /** Number of index-incompatible schema type changes. */
+    private final int mIndexIncompatibleTypeChangeCount;
+
+    /** Number of backwards-incompatible schema type changes. */
+    private final int mBackwardsIncompatibleTypeChangeCount;
+
+    SetSchemaStats(@NonNull Builder builder) {
+        Objects.requireNonNull(builder);
+        mPackageName = builder.mPackageName;
+        mDatabase = builder.mDatabase;
+        mStatusCode = builder.mStatusCode;
+        mTotalLatencyMillis = builder.mTotalLatencyMillis;
+        mNativeLatencyMillis = builder.mNativeLatencyMillis;
+        mNewTypeCount = builder.mNewTypeCount;
+        mDeletedTypeCount = builder.mDeletedTypeCount;
+        mCompatibleTypeChangeCount = builder.mCompatibleTypeChangeCount;
+        mIndexIncompatibleTypeChangeCount = builder.mIndexIncompatibleTypeChangeCount;
+        mBackwardsIncompatibleTypeChangeCount = builder.mBackwardsIncompatibleTypeChangeCount;
+    }
+
+    /** Returns calling package name. */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /** Returns calling database name. */
+    @NonNull
+    public String getDatabase() {
+        return mDatabase;
+    }
+
+    /** Returns status of the SetSchema action. */
+    @AppSearchResult.ResultCode
+    public int getStatusCode() {
+        return mStatusCode;
+    }
+
+    /** Returns the total latency of the SetSchema action. */
+    public int getTotalLatencyMillis() {
+        return mTotalLatencyMillis;
+    }
+
+    /** Returns overall time used for the native function call. */
+    public int getNativeLatencyMillis() {
+        return mNativeLatencyMillis;
+    }
+
+    /** Returns number of newly added schema types. */
+    public int getNewTypeCount() {
+        return mNewTypeCount;
+    }
+
+    /** Returns number of deleted schema types. */
+    public int getDeletedTypeCount() {
+        return mDeletedTypeCount;
+    }
+
+    /** Returns number of compatible type changes. */
+    public int getCompatibleTypeChangeCount() {
+        return mCompatibleTypeChangeCount;
+    }
+
+    /**
+     * Returns number of index-incompatible type change.
+     *
+     * <p>An index-incompatible type change is one that affects how pre-existing data should be
+     * searched over, such as modifying the {@code IndexingType} of an existing property.
+     */
+    public int getIndexIncompatibleTypeChangeCount() {
+        return mIndexIncompatibleTypeChangeCount;
+    }
+
+    /**
+     * Returns number of backwards-incompatible type change.
+     *
+     * <p>For details on what constitutes a backward-incompatible type change, please see {@link
+     * android.app.appsearch.SetSchemaRequest}.
+     */
+    public int getBackwardsIncompatibleTypeChangeCount() {
+        return mBackwardsIncompatibleTypeChangeCount;
+    }
+
+    /** Builder for {@link SetSchemaStats}. */
+    public static class Builder {
+        @NonNull final String mPackageName;
+        @NonNull final String mDatabase;
+        @AppSearchResult.ResultCode int mStatusCode;
+        int mTotalLatencyMillis;
+        int mNativeLatencyMillis;
+        int mNewTypeCount;
+        int mDeletedTypeCount;
+        int mCompatibleTypeChangeCount;
+        int mIndexIncompatibleTypeChangeCount;
+        int mBackwardsIncompatibleTypeChangeCount;
+
+        /** Constructor for the {@link Builder}. */
+        public Builder(@NonNull String packageName, @NonNull String database) {
+            mPackageName = Objects.requireNonNull(packageName);
+            mDatabase = Objects.requireNonNull(database);
+        }
+
+        /** Sets the status of the SetSchema action. */
+        @NonNull
+        public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) {
+            mStatusCode = statusCode;
+            return this;
+        }
+
+        /** Sets total latency for the SetSchema action. */
+        @NonNull
+        public Builder setTotalLatencyMillis(int totalLatencyMillis) {
+            mTotalLatencyMillis = totalLatencyMillis;
+            return this;
+        }
+
+        /** Sets native latency in milliseconds. */
+        @NonNull
+        public Builder setNativeLatencyMillis(int nativeLatencyMillis) {
+            mNativeLatencyMillis = nativeLatencyMillis;
+            return this;
+        }
+
+        /** Sets number of new types. */
+        @NonNull
+        public Builder setNewTypeCount(int newTypeCount) {
+            mNewTypeCount = newTypeCount;
+            return this;
+        }
+
+        /** Sets number of deleted types. */
+        @NonNull
+        public Builder setDeletedTypeCount(int deletedTypeCount) {
+            mDeletedTypeCount = deletedTypeCount;
+            return this;
+        }
+
+        /** Sets number of compatible type changes. */
+        @NonNull
+        public Builder setCompatibleTypeChangeCount(int compatibleTypeChangeCount) {
+            mCompatibleTypeChangeCount = compatibleTypeChangeCount;
+            return this;
+        }
+
+        /** Sets number of index-incompatible type changes. */
+        @NonNull
+        public Builder setIndexIncompatibleTypeChangeCount(int indexIncompatibleTypeChangeCount) {
+            mIndexIncompatibleTypeChangeCount = indexIncompatibleTypeChangeCount;
+            return this;
+        }
+
+        /** Sets number of backwards-incompatible type changes. */
+        @NonNull
+        public Builder setBackwardsIncompatibleTypeChangeCount(
+                int backwardsIncompatibleTypeChangeCount) {
+            mBackwardsIncompatibleTypeChangeCount = backwardsIncompatibleTypeChangeCount;
+            return this;
+        }
+
+        /** Builds a new {@link SetSchemaStats} from the {@link Builder}. */
+        @NonNull
+        public SetSchemaStats build() {
+            return new SetSchemaStats(/* builder= */ this);
+        }
+    }
+}
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 277740f..d07a3b9 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-Ic6be29e84e7c6f31cdae37973850bb3395920326
+Ibbf4260deb720ce724be81ee4394ea96181ee0f7
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchEmail.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchEmail.java
index 845274d..d28d4ac 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchEmail.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchEmail.java
@@ -165,43 +165,37 @@
         /** Sets the from address of {@link AppSearchEmail} */
         @NonNull
         public Builder setFrom(@NonNull String from) {
-            setPropertyString(KEY_FROM, from);
-            return this;
+            return setPropertyString(KEY_FROM, from);
         }
 
         /** Sets the destination address of {@link AppSearchEmail} */
         @NonNull
         public Builder setTo(@NonNull String... to) {
-            setPropertyString(KEY_TO, to);
-            return this;
+            return setPropertyString(KEY_TO, to);
         }
 
         /** Sets the CC list of {@link AppSearchEmail} */
         @NonNull
         public Builder setCc(@NonNull String... cc) {
-            setPropertyString(KEY_CC, cc);
-            return this;
+            return setPropertyString(KEY_CC, cc);
         }
 
         /** Sets the BCC list of {@link AppSearchEmail} */
         @NonNull
         public Builder setBcc(@NonNull String... bcc) {
-            setPropertyString(KEY_BCC, bcc);
-            return this;
+            return setPropertyString(KEY_BCC, bcc);
         }
 
         /** Sets the subject of {@link AppSearchEmail} */
         @NonNull
         public Builder setSubject(@NonNull String subject) {
-            setPropertyString(KEY_SUBJECT, subject);
-            return this;
+            return setPropertyString(KEY_SUBJECT, subject);
         }
 
         /** Sets the body of {@link AppSearchEmail} */
         @NonNull
         public Builder setBody(@NonNull String body) {
-            setPropertyString(KEY_BODY, body);
-            return this;
+            return setPropertyString(KEY_BODY, body);
         }
 
         /** Builds the {@link AppSearchEmail} object. */
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 9dd1296..42e953b 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -344,6 +344,9 @@
      */
     public static final int REASON_MEDIA_SESSION_CALLBACK = 317;
 
+    /** @hide The app requests out-out. */
+    public static final int REASON_OPT_OUT_REQUESTED = 1000;
+
     /**
      * The list of BG-FGS-Launch and temp-allow-list reason code.
      * @hide
@@ -411,6 +414,7 @@
             REASON_EVENT_MMS,
             REASON_SHELL,
             REASON_MEDIA_SESSION_CALLBACK,
+            REASON_OPT_OUT_REQUESTED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ReasonCode {}
@@ -709,6 +713,8 @@
                 return "SHELL";
             case REASON_MEDIA_SESSION_CALLBACK:
                 return "MEDIA_SESSION_CALLBACK";
+            case REASON_OPT_OUT_REQUESTED:
+                return "REASON_OPT_OUT_REQUESTED";
             default:
                 return "(unknown:" + reasonCode + ")";
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index ebf4ed0..4b081d2 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -180,7 +180,7 @@
             COMPRESS_TIME ? 1 * ONE_MINUTE : 12 * ONE_HOUR,
             COMPRESS_TIME ? 4 * ONE_MINUTE : 24 * ONE_HOUR,
             COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR,
-            COMPRESS_TIME ? 32 * ONE_MINUTE : 30 * ONE_DAY
+            COMPRESS_TIME ? 32 * ONE_MINUTE : 45 * ONE_DAY
     };
 
     /** The minimum allowed values for each index in {@link #DEFAULT_ELAPSED_TIME_THRESHOLDS}. */
diff --git a/build/boot/boot-image-profile.txt b/boot/boot-image-profile.txt
similarity index 100%
rename from build/boot/boot-image-profile.txt
rename to boot/boot-image-profile.txt
diff --git a/build/boot/preloaded-classes b/boot/preloaded-classes
similarity index 100%
rename from build/boot/preloaded-classes
rename to boot/preloaded-classes
diff --git a/core/api/current.txt b/core/api/current.txt
index 5f3eeb1..324ad33 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -24711,32 +24711,37 @@
     method @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) public int getSubErrorCode();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.PlaybackErrorEvent> CREATOR;
-    field public static final int ERROR_AUDIOTRACK_INIT = 17; // 0x11
-    field public static final int ERROR_AUDIOTRACK_OTHER = 19; // 0x13
-    field public static final int ERROR_AUDIOTRACK_WRITE = 18; // 0x12
-    field public static final int ERROR_DECODER_DECODE = 14; // 0xe
-    field public static final int ERROR_DECODER_INIT = 13; // 0xd
-    field public static final int ERROR_DECODER_OOM = 15; // 0xf
-    field public static final int ERROR_DECODER_OTHER = 16; // 0x10
+    field public static final int ERROR_AUDIO_TRACK_INIT_FAILED = 17; // 0x11
+    field public static final int ERROR_AUDIO_TRACK_OTHER = 19; // 0x13
+    field public static final int ERROR_AUDIO_TRACK_WRITE_FAILED = 18; // 0x12
+    field public static final int ERROR_DECODER_INIT_FAILED = 13; // 0xd
+    field public static final int ERROR_DECODING_FAILED = 14; // 0xe
+    field public static final int ERROR_DECODING_FORMAT_EXCEEDS_CAPABILITIES = 15; // 0xf
+    field public static final int ERROR_DECODING_FORMAT_UNSUPPORTED = 35; // 0x23
+    field public static final int ERROR_DECODING_OTHER = 16; // 0x10
     field public static final int ERROR_DRM_CONTENT_ERROR = 28; // 0x1c
-    field public static final int ERROR_DRM_DISALLOWED = 26; // 0x1a
-    field public static final int ERROR_DRM_LICENSE_ERROR = 25; // 0x19
+    field public static final int ERROR_DRM_DEVICE_REVOKED = 29; // 0x1d
+    field public static final int ERROR_DRM_DISALLOWED_OPERATION = 26; // 0x1a
+    field public static final int ERROR_DRM_LICENSE_ACQUISITION_FAILED = 25; // 0x19
     field public static final int ERROR_DRM_OTHER = 30; // 0x1e
     field public static final int ERROR_DRM_PROVISIONING_FAILED = 24; // 0x18
-    field public static final int ERROR_DRM_REVOKED = 29; // 0x1d
+    field public static final int ERROR_DRM_SCHEME_UNSUPPORTED = 23; // 0x17
     field public static final int ERROR_DRM_SYSTEM_ERROR = 27; // 0x1b
-    field public static final int ERROR_DRM_UNAVAILABLE = 23; // 0x17
-    field public static final int ERROR_MEDIA_MANIFEST = 10; // 0xa
-    field public static final int ERROR_MEDIA_OTHER = 12; // 0xc
-    field public static final int ERROR_MEDIA_PARSER = 11; // 0xb
-    field public static final int ERROR_NETWORK_BAD_STATUS = 5; // 0x5
-    field public static final int ERROR_NETWORK_CLOSED = 8; // 0x8
-    field public static final int ERROR_NETWORK_CONNECT = 4; // 0x4
-    field public static final int ERROR_NETWORK_DNS = 6; // 0x6
-    field public static final int ERROR_NETWORK_OFFLINE = 3; // 0x3
-    field public static final int ERROR_NETWORK_OTHER = 9; // 0x9
-    field public static final int ERROR_NETWORK_TIMEOUT = 7; // 0x7
+    field public static final int ERROR_IO_BAD_HTTP_STATUS = 5; // 0x5
+    field public static final int ERROR_IO_CONNECTION_CLOSED = 8; // 0x8
+    field public static final int ERROR_IO_CONNECTION_TIMEOUT = 7; // 0x7
+    field public static final int ERROR_IO_DNS_FAILED = 6; // 0x6
+    field public static final int ERROR_IO_FILE_NOT_FOUND = 31; // 0x1f
+    field public static final int ERROR_IO_NETWORK_CONNECTION_FAILED = 4; // 0x4
+    field public static final int ERROR_IO_NETWORK_UNAVAILABLE = 3; // 0x3
+    field public static final int ERROR_IO_NO_PERMISSION = 32; // 0x20
+    field public static final int ERROR_IO_OTHER = 9; // 0x9
     field public static final int ERROR_OTHER = 1; // 0x1
+    field public static final int ERROR_PARSING_CONTAINER_MALFORMED = 11; // 0xb
+    field public static final int ERROR_PARSING_CONTAINER_UNSUPPORTED = 34; // 0x22
+    field public static final int ERROR_PARSING_MANIFEST_MALFORMED = 10; // 0xa
+    field public static final int ERROR_PARSING_MANIFEST_UNSUPPORTED = 33; // 0x21
+    field public static final int ERROR_PARSING_OTHER = 12; // 0xc
     field public static final int ERROR_PLAYER_BEHIND_LIVE_WINDOW = 21; // 0x15
     field public static final int ERROR_PLAYER_OTHER = 22; // 0x16
     field public static final int ERROR_PLAYER_REMOTE = 20; // 0x14
@@ -52983,7 +52988,7 @@
     method @NonNull public android.view.translation.TranslationRequestValue getValue(@NonNull String);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.ViewTranslationRequest> CREATOR;
-    field public static final String ID_TEXT = "text";
+    field public static final String ID_TEXT = "android:text";
   }
 
   public static final class ViewTranslationRequest.Builder {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d366e35d..f458107 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -50,6 +50,10 @@
     field public static final String UNDEFINED = "android.permission-group.UNDEFINED";
   }
 
+  public static final class R.attr {
+    field public static final int requestForegroundServiceExemption;
+  }
+
   public static final class R.bool {
     field public static final int config_assistantOnTopOfDream = 17891333; // 0x1110005
     field public static final int config_perDisplayFocusEnabled = 17891332; // 0x1110004
@@ -452,7 +456,7 @@
     method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs();
     method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs();
-    method public void forceUpdateUserSetupComplete();
+    method public void forceUpdateUserSetupComplete(int);
     method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages();
     method @NonNull public java.util.Set<java.lang.String> getDisallowedSystemApps(@NonNull android.content.ComponentName, int, @NonNull String);
     method public long getLastBugReportRequestTime();
@@ -806,6 +810,7 @@
   }
 
   public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+    method public boolean hasRequestForegroundServiceExemption();
     method public boolean isPrivilegedApp();
     method public boolean isSystemApp();
     field public static final int PRIVATE_FLAG_PRIVILEGED = 8; // 0x8
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 02e64b8..38b19ae 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -11974,15 +11974,14 @@
 
     /**
      * @hide
-     * Force update user setup completed status.
+     * Force update user setup completed status for the given {@code userId}.
      * @throws {@link SecurityException} if the caller has no
-     *         {@code android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS} or the caller is
-     *         not {@link UserHandle#SYSTEM_USER}
+     *         {@code android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}.
      */
     @TestApi
-    public void forceUpdateUserSetupComplete() {
+    public void forceUpdateUserSetupComplete(@UserIdInt int userId) {
         try {
-            mService.forceUpdateUserSetupComplete();
+            mService.forceUpdateUserSetupComplete(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9f76bd1..db2fc0d 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -406,7 +406,7 @@
     boolean isDeviceProvisioningConfigApplied();
     void setDeviceProvisioningConfigApplied();
 
-    void forceUpdateUserSetupComplete();
+    void forceUpdateUserSetupComplete(int userId);
 
     void setBackupServiceEnabled(in ComponentName admin, boolean enabled);
     boolean isBackupServiceEnabled(in ComponentName admin);
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 1cf4567..8c59982 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -189,6 +189,7 @@
      * detected.
      * <li> {@code eio} indicates that an I/O error will be returned for an attempt to read
      * corrupted data blocks.
+     * <li> {@code disabled} indicates that integrity check is disabled.
      * For details see Verified Boot documentation.
      */
     public static final int TAG_OS_STARTUP = SecurityLogTags.SECURITY_OS_STARTUP;
@@ -344,7 +345,7 @@
     public static final int TAG_WIPE_FAILURE = SecurityLogTags.SECURITY_WIPE_FAILED;
 
     /**
-     * Indicates that an authentication key was generated. The log entry contains the following
+     * Indicates that a cryptographic key was generated. The log entry contains the following
      * information about the event, encapsulated in an {@link Object} array and accessible via
      * {@link SecurityEvent#getData()}:
      * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index a3e0473..8b0e992 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -778,9 +778,18 @@
      */
     public static final int PRIVATE_FLAG_EXT_PROFILEABLE = 1 << 0;
 
+    /**
+     * Value for {@link #privateFlagsExt}: whether this application has requested
+     * exemption from the foreground service restriction introduced in S
+     * (https://developer.android.com/about/versions/12/foreground-services).
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION = 1 << 1;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "PRIVATE_FLAG_EXT_" }, value = {
             PRIVATE_FLAG_EXT_PROFILEABLE,
+            PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApplicationInfoPrivateFlagsExt {}
@@ -2445,6 +2454,17 @@
     }
 
     /**
+     * @return whether the app has requested exemption from the foreground service restrictions.
+     * This does not take any affect for now.
+     * @hide
+     */
+    @TestApi
+    public boolean hasRequestForegroundServiceExemption() {
+        return (privateFlagsExt
+                & ApplicationInfo.PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION) != 0;
+    }
+
+    /**
      * @hide
      */
     @Override protected ApplicationInfo getApplicationInfo() {
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index 980f10d..75fa1c1 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -806,7 +806,9 @@
     public static int appInfoPrivateFlagsExt(ParsingPackageRead pkg) {
         // @formatter:off
         int privateFlagsExt =
-                flag(pkg.isProfileable(), ApplicationInfo.PRIVATE_FLAG_EXT_PROFILEABLE);
+                flag(pkg.isProfileable(), ApplicationInfo.PRIVATE_FLAG_EXT_PROFILEABLE)
+                | flag(pkg.hasRequestForegroundServiceExemption(),
+                        ApplicationInfo.PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION);
         // @formatter:on
         return privateFlagsExt;
     }
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index cea50cb..2413e6d 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -338,6 +338,8 @@
 
     ParsingPackage setTheme(int theme);
 
+    ParsingPackage setRequestForegroundServiceExemption(boolean requestForegroundServiceExemption);
+
     ParsingPackage setUpgradeKeySets(@NonNull Set<String> upgradeKeySets);
 
     ParsingPackage setUse32BitAbi(boolean use32BitAbi);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 7114886..b0342aa 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -464,6 +464,7 @@
                 CROSS_PROFILE,
                 ENABLED,
                 DISALLOW_PROFILING,
+                REQUEST_FOREGROUND_SERVICE_EXEMPTION,
         })
         public @interface Values {}
         private static final long EXTERNAL_STORAGE = 1L;
@@ -512,6 +513,7 @@
         private static final long CROSS_PROFILE = 1L << 43;
         private static final long ENABLED = 1L << 44;
         private static final long DISALLOW_PROFILING = 1L << 45;
+        private static final long REQUEST_FOREGROUND_SERVICE_EXEMPTION = 1L << 46;
     }
 
     private ParsingPackageImpl setBoolean(@Booleans.Values long flag, boolean value) {
@@ -2199,6 +2201,11 @@
     }
 
     @Override
+    public boolean hasRequestForegroundServiceExemption() {
+        return getBoolean(Booleans.REQUEST_FOREGROUND_SERVICE_EXEMPTION);
+    }
+
+    @Override
     public ParsingPackageImpl setBaseRevisionCode(int value) {
         baseRevisionCode = value;
         return this;
@@ -2420,6 +2427,11 @@
     }
 
     @Override
+    public ParsingPackageImpl setRequestForegroundServiceExemption(boolean value) {
+        return setBoolean(Booleans.REQUEST_FOREGROUND_SERVICE_EXEMPTION, value);
+    }
+
+    @Override
     public ParsingPackageImpl setUiOptions(int value) {
         uiOptions = value;
         return this;
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index 1c2c59f..35a2b9a 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -906,10 +906,15 @@
      */
     @ApplicationInfo.NativeHeapZeroInitialized
     int getNativeHeapZeroInitialized();
-
     @Nullable
     Boolean hasRequestRawExternalStorageAccess();
 
+    /**
+     * @see ApplicationInfo#hasRequestForegroundServiceExemption()
+     * @see R.styleable#AndroidManifest_requestForegroundServiceExemption
+     */
+    boolean hasRequestForegroundServiceExemption();
+
     // TODO(b/135203078): Hide and enforce going through PackageInfoUtils
     ApplicationInfo toAppInfoWithoutState();
 
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 22d75ef..5d74e74 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -2033,6 +2033,12 @@
                                 .AndroidManifestApplication_requestRawExternalStorageAccess,
                         false));
             }
+            if (sa.hasValue(
+                    R.styleable.AndroidManifestApplication_requestForegroundServiceExemption)) {
+                pkg.setRequestForegroundServiceExemption(sa.getBoolean(R.styleable
+                                .AndroidManifestApplication_requestForegroundServiceExemption,
+                        false));
+            }
         } finally {
             sa.recycle();
         }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index d7b96df..f0d410f 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1329,6 +1329,7 @@
                 WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
         mWindow.getWindow().getAttributes().setFitInsetsTypes(statusBars() | navigationBars());
         mWindow.getWindow().getAttributes().setFitInsetsSides(Side.all() & ~Side.BOTTOM);
+        mWindow.getWindow().getAttributes().receiveInsetsIgnoringZOrder = true;
 
         // Automotive devices may request the navigation bar to be hidden when the IME shows up
         // (controlled via config_automotiveHideNavBarForKeyboard) in order to maximize the visible
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 98acd98..01d1aa5 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -79,6 +79,16 @@
     public static final int DIRECTION_OUT = 1;
 
     /**
+     * Used when applying a transform to direct traffic through an {@link IpSecTransform} for
+     * forwarding between interfaces.
+     *
+     * <p>See {@link #applyTransportModeTransform(Socket, int, IpSecTransform)}.
+     *
+     * @hide
+     */
+    public static final int DIRECTION_FWD = 2;
+
+    /**
      * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
      *
      * <p>No IPsec packet may contain an SPI of 0.
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 5b3bc26..38ed3fb 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -437,7 +437,8 @@
          * Magic version number for a current development build, which has
          * not yet turned into an official release.
          */
-        public static final int CUR_DEVELOPMENT = VMRuntime.SDK_VERSION_CUR_DEVELOPMENT;
+        // This must match VMRuntime.SDK_VERSION_CUR_DEVELOPMENT.
+        public static final int CUR_DEVELOPMENT = 10000;
 
         /**
          * October 2008: The original, first, version of Android.  Yay!
@@ -1324,6 +1325,7 @@
      * selinux into "permissive" mode in particular.
      * @hide
      */
+    @UnsupportedAppUsage
     public static final boolean IS_DEBUGGABLE =
             SystemProperties.getInt("ro.debuggable", 0) == 1;
 
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index b8bb353..d90e129 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -74,8 +74,9 @@
      *
      * @deprecated Accurate counting is a burden on the runtime and may be removed.
      */
+    // This must match VMDebug.TRACE_COUNT_ALLOCS.
     @Deprecated
-    public static final int TRACE_COUNT_ALLOCS  = VMDebug.TRACE_COUNT_ALLOCS;
+    public static final int TRACE_COUNT_ALLOCS  = 1;
 
     /**
      * Flags for printLoadedClasses().  Default behavior is to only show
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index 5f41174..473e7ae 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -16,9 +16,6 @@
 
 package android.service.voice;
 
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
-import android.annotation.CallSuper;
 import android.annotation.DurationMillisLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -33,10 +30,8 @@
 import android.hardware.soundtrigger.SoundTrigger;
 import android.media.AudioFormat;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
-import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
@@ -67,6 +62,8 @@
  * to inform the system that a keyphrase was not detected. The system then relays this result to
  * the {@link VoiceInteractionService} through {@link HotwordDetector.Callback}.
  *
+ * Note: Methods in this class may be called concurrently
+ *
  * @hide
  */
 @SystemApi
@@ -138,8 +135,6 @@
     public static final String SERVICE_INTERFACE =
             "android.service.voice.HotwordDetectionService";
 
-    private Handler mHandler;
-
     @Nullable
     private ContentCaptureManager mContentCaptureManager;
 
@@ -154,13 +149,12 @@
             if (DBG) {
                 Log.d(TAG, "#detectFromDspSource");
             }
-            mHandler.sendMessage(obtainMessage(HotwordDetectionService::onDetect,
-                    HotwordDetectionService.this,
+            HotwordDetectionService.this.onDetect(
                     new AlwaysOnHotwordDetector.EventPayload(
                             event.triggerInData, event.captureAvailable,
                             event.captureFormat, event.captureSession, event.data),
                     timeoutMillis,
-                    new Callback(callback)));
+                    new Callback(callback));
         }
 
         @Override
@@ -169,11 +163,10 @@
             if (DBG) {
                 Log.d(TAG, "#updateState");
             }
-            mHandler.sendMessage(obtainMessage(HotwordDetectionService::onUpdateStateInternal,
-                    HotwordDetectionService.this,
+            HotwordDetectionService.this.onUpdateStateInternal(
                     options,
                     sharedMemory,
-                    callback));
+                    callback);
         }
 
         @Override
@@ -189,19 +182,15 @@
             }
             switch (audioSource) {
                 case AUDIO_SOURCE_MICROPHONE:
-                    mHandler.sendMessage(obtainMessage(
-                            HotwordDetectionService::onDetect,
-                            HotwordDetectionService.this,
-                            new Callback(callback)));
+                    HotwordDetectionService.this.onDetect(
+                            new Callback(callback));
                     break;
                 case AUDIO_SOURCE_EXTERNAL:
-                    mHandler.sendMessage(obtainMessage(
-                            HotwordDetectionService::onDetect,
-                            HotwordDetectionService.this,
+                    HotwordDetectionService.this.onDetect(
                             audioStream,
                             audioFormat,
                             options,
-                            new Callback(callback)));
+                            new Callback(callback));
                     break;
                 default:
                     Log.i(TAG, "Unsupported audio source " + audioSource);
@@ -216,13 +205,6 @@
         }
     };
 
-    @CallSuper
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mHandler = Handler.createAsync(Looper.getMainLooper());
-    }
-
     @Override
     @Nullable
     public final IBinder onBind(@NonNull Intent intent) {
diff --git a/core/java/android/text/method/TranslationTransformationMethod.java b/core/java/android/text/method/TranslationTransformationMethod.java
index 54c0ffc..80387aa 100644
--- a/core/java/android/text/method/TranslationTransformationMethod.java
+++ b/core/java/android/text/method/TranslationTransformationMethod.java
@@ -22,6 +22,7 @@
 import android.util.Log;
 import android.view.View;
 import android.view.translation.TranslationResponseValue;
+import android.view.translation.ViewTranslationRequest;
 import android.view.translation.ViewTranslationResponse;
 import android.widget.TextView;
 
@@ -67,7 +68,8 @@
             Log.w(TAG, "Caller did not enable length changes; not transforming to translated text");
             return source;
         }
-        TranslationResponseValue value = mTranslationResponse.getValue("text");
+        TranslationResponseValue value = mTranslationResponse.getValue(
+                ViewTranslationRequest.ID_TEXT);
         CharSequence translatedText;
         if (value.getStatusCode() == TranslationResponseValue.STATUS_SUCCESS) {
             translatedText = value.getText();
diff --git a/core/java/android/view/translation/ViewTranslationRequest.java b/core/java/android/view/translation/ViewTranslationRequest.java
index 2913dfa..00f6ef2 100644
--- a/core/java/android/view/translation/ViewTranslationRequest.java
+++ b/core/java/android/view/translation/ViewTranslationRequest.java
@@ -43,7 +43,7 @@
      * Constant id for the default view text to be translated. This is used by
      * {@link Builder#setValue(String, TranslationRequestValue)}.
      */
-    public static final String ID_TEXT = "text";
+    public static final String ID_TEXT = "android:text";
 
     /**
      * The {@link AutofillId} of the view associated with this request.
@@ -303,7 +303,7 @@
     };
 
     @DataClass.Generated(
-            time = 1617119791798L,
+            time = 1620259482911L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/translation/ViewTranslationRequest.java",
             inputSignatures = "public static final  java.lang.String ID_TEXT\nprivate final @android.annotation.NonNull android.view.autofill.AutofillId mAutofillId\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"translationRequestValue\") java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue> mTranslationRequestValues\npublic @android.annotation.NonNull android.view.translation.TranslationRequestValue getValue(java.lang.String)\npublic @android.annotation.NonNull java.util.Set<java.lang.String> getKeys()\npublic @android.annotation.NonNull android.view.autofill.AutofillId getAutofillId()\nprivate static  java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue> defaultTranslationRequestValues()\nclass ViewTranslationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.NonNull android.view.autofill.AutofillId mAutofillId\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue> mTranslationRequestValues\nprivate  long mBuilderFieldsSet\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.view.translation.ViewTranslationRequest.Builder setValue(java.lang.String,android.view.translation.TranslationRequestValue)\npublic @android.annotation.NonNull android.view.translation.ViewTranslationRequest build()\n  android.view.translation.ViewTranslationRequest.Builder setTranslationRequestValues(java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue>)\nprivate  void checkNotUsed()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genToString=true, genEqualsHashCode=true, genGetters=false, genHiddenConstructor=true)")
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 4d5b90a..4126801 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -401,8 +401,9 @@
 
         int totalFramesCount = 0;
         long maxFrameTimeNanos = 0;
+        int missedFramesCount = 0;
         int missedAppFramesCount = 0;
-        int missedSfFramesCounts = 0;
+        int missedSfFramesCount = 0;
 
         for (int i = 0; i <= indexOnOrAfterEnd; i++) {
             JankInfo info = mJankInfos.valueAt(i);
@@ -411,17 +412,23 @@
             }
             if (info.surfaceControlCallbackFired) {
                 totalFramesCount++;
+                boolean missedFrame = false;
                 if ((info.jankType & PREDICTION_ERROR) != 0
                         || ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0)) {
                     Log.w(TAG, "Missed App frame:" + info.jankType);
                     missedAppFramesCount++;
+                    missedFrame = true;
                 }
                 if ((info.jankType & DISPLAY_HAL) != 0
                         || (info.jankType & JANK_SURFACEFLINGER_DEADLINE_MISSED) != 0
                         || (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0
                         || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0) {
                     Log.w(TAG, "Missed SF frame:" + info.jankType);
-                    missedSfFramesCounts++;
+                    missedSfFramesCount++;
+                    missedFrame = true;
+                }
+                if (missedFrame) {
+                    missedFramesCount++;
                 }
                 // TODO (b/174755489): Early latch currently gets fired way too often, so we have
                 // to ignore it for now.
@@ -438,10 +445,12 @@
         }
 
         // Log the frame stats as counters to make them easily accessible in traces.
+        Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedFrames",
+                missedFramesCount);
         Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedAppFrames",
                 missedAppFramesCount);
         Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedSfFrames",
-                missedSfFramesCounts);
+                missedSfFramesCount);
         Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#totalFrames",
                 totalFramesCount);
         Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxFrameTimeMillis",
@@ -449,7 +458,7 @@
 
         // Trigger perfetto if necessary.
         boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
-                && missedAppFramesCount + missedSfFramesCounts >= mTraceThresholdMissedFrames;
+                && missedFramesCount >= mTraceThresholdMissedFrames;
         boolean overFrameTimeThreshold = mTraceThresholdFrameTimeMillis != -1
                 && maxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND;
         if (overMissedFramesThreshold || overFrameTimeThreshold) {
@@ -460,9 +469,10 @@
                     FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED,
                     mSession.getStatsdInteractionType(),
                     totalFramesCount,
-                    missedAppFramesCount + missedSfFramesCounts,
+                    missedFramesCount,
                     maxFrameTimeNanos,
-                    missedSfFramesCounts);
+                    missedSfFramesCount,
+                    missedAppFramesCount);
             if (mListener != null) {
                 mListener.onCujEvents(mSession, ACTION_METRICS_LOGGED);
             }
@@ -472,7 +482,8 @@
                     + " (" + mBeginVsyncId + "," + mEndVsyncId + ")"
                     + " totalFrames=" + totalFramesCount
                     + " missedAppFrames=" + missedAppFramesCount
-                    + " missedSfFrames=" + missedSfFramesCounts
+                    + " missedSfFrames=" + missedSfFramesCount
+                    + " missedFrames=" + missedFramesCount
                     + " maxFrameTimeMillis=" + maxFrameTimeNanos / NANOS_IN_MILLISECOND);
         }
     }
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index f19a123..5ba1928 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -271,4 +271,9 @@
      * @param enable {@code true} if enable, otherwise set to {@code false}.
      */
     void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable);
+
+    /**
+     * Triggers a GC in the system and status bar.
+     */
+    void runGcForTest();
 }
diff --git a/core/java/com/android/internal/util/GcUtils.java b/core/java/com/android/internal/util/GcUtils.java
new file mode 100644
index 0000000..e37ba3c
--- /dev/null
+++ b/core/java/com/android/internal/util/GcUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.util.Slog;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A helper class to handle gc'ing a process, mainly used for testing.
+ *
+ * @hide
+ */
+public final class GcUtils {
+    private static final String TAG = GcUtils.class.getSimpleName();
+
+    /**
+     * Runs a GC and attempts to wait for finalization.
+     */
+    public static void runGcAndFinalizersSync() {
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+
+        final CountDownLatch fence = new CountDownLatch(1);
+        createFinalizationObserver(fence);
+        try {
+            do {
+                Runtime.getRuntime().gc();
+                Runtime.getRuntime().runFinalization();
+            } while (!fence.await(100, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException ex) {
+            throw new RuntimeException(ex);
+        }
+        Slog.v(TAG, "Running gc and finalizers");
+    }
+
+    /**
+     * Create the observer in the scope of a method to minimize the chance that
+     * it remains live in a DEX/machine register at the point of the fence guard.
+     * This must be kept to avoid R8 inlining it.
+     */
+    private static void createFinalizationObserver(CountDownLatch fence) {
+        new Object() {
+            @Override
+            protected void finalize() throws Throwable {
+                try {
+                    fence.countDown();
+                } finally {
+                    super.finalize();
+                }
+            }
+        };
+    }
+
+    // Uninstantiable
+    private GcUtils() {}
+}
diff --git a/core/jni/com_android_internal_net_NetworkUtilsInternal.cpp b/core/jni/com_android_internal_net_NetworkUtilsInternal.cpp
index 980e12d..83e2f2b 100644
--- a/core/jni/com_android_internal_net_NetworkUtilsInternal.cpp
+++ b/core/jni/com_android_internal_net_NetworkUtilsInternal.cpp
@@ -31,7 +31,7 @@
 }
 
 static jboolean android_net_utils_protectFromVpnWithFd(JNIEnv *env, jobject thiz, jobject javaFd) {
-    return android_net_utils_protectFromVpn(env, thiz, AFileDescriptor_getFD(env, javaFd));
+    return android_net_utils_protectFromVpn(env, thiz, AFileDescriptor_getFd(env, javaFd));
 }
 
 static const JNINativeMethod gNetworkUtilMethods[] = {
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c5f4fd1..6386274 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1954,6 +1954,13 @@
          See the <a href="{@docRoot}about/versions/12/backup-restore">Changes in backup and restore</a>
          document for the format of the XML file.-->
         <attr name="dataExtractionRules" format="reference"/>
+
+        <!-- @hide Request exemption from the foreground service restrictions introduced in S
+        (https://developer.android.com/about/versions/12/foreground-services)
+        Note the framework <b>ignores</b> this attribute at this time. Once apps target S or above,
+        there's no way to be exempted (without using a privileged permission).
+        -->
+        <attr name="requestForegroundServiceExemption" format="boolean" />
     </declare-styleable>
 
     <!-- An attribution is a logical part of an app and is identified by a tag.
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 500a9da..f7a9930 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3099,6 +3099,8 @@
     <public name="durationBetweenRequestsMillis" />
     <public name="showInInputMethodPicker" />
     <public name="effectColor" />
+    <!-- @hide @TestApi -->
+    <public name="requestForegroundServiceExemption" />
   </staging-public-group>
 
   <staging-public-group type="drawable" first-id="0x010800b5">
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaResponseTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaResponseTest.java
new file mode 100644
index 0000000..0bbea42
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaResponseTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class SetSchemaResponseTest {
+    @Test
+    public void testRebuild() {
+        SetSchemaResponse.MigrationFailure failure1 =
+                new SetSchemaResponse.MigrationFailure(
+                        "namespace",
+                        "failure1",
+                        "schemaType",
+                        AppSearchResult.newFailedResult(
+                                AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage"));
+        SetSchemaResponse.MigrationFailure failure2 =
+                new SetSchemaResponse.MigrationFailure(
+                        "namespace",
+                        "failure2",
+                        "schemaType",
+                        AppSearchResult.newFailedResult(
+                                AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage"));
+
+        SetSchemaResponse original =
+                new SetSchemaResponse.Builder()
+                        .addDeletedType("delete1")
+                        .addIncompatibleType("incompatible1")
+                        .addMigratedType("migrated1")
+                        .addMigrationFailure(failure1)
+                        .build();
+        assertThat(original.getDeletedTypes()).containsExactly("delete1");
+        assertThat(original.getIncompatibleTypes()).containsExactly("incompatible1");
+        assertThat(original.getMigratedTypes()).containsExactly("migrated1");
+        assertThat(original.getMigrationFailures()).containsExactly(failure1);
+
+        SetSchemaResponse rebuild =
+                original.toBuilder()
+                        .addDeletedType("delete2")
+                        .addIncompatibleType("incompatible2")
+                        .addMigratedType("migrated2")
+                        .addMigrationFailure(failure2)
+                        .build();
+
+        // rebuild won't effect the original object
+        assertThat(original.getDeletedTypes()).containsExactly("delete1");
+        assertThat(original.getIncompatibleTypes()).containsExactly("incompatible1");
+        assertThat(original.getMigratedTypes()).containsExactly("migrated1");
+        assertThat(original.getMigrationFailures()).containsExactly(failure1);
+
+        assertThat(rebuild.getDeletedTypes()).containsExactly("delete1", "delete2");
+        assertThat(rebuild.getIncompatibleTypes())
+                .containsExactly("incompatible1", "incompatible2");
+        assertThat(rebuild.getMigratedTypes()).containsExactly("migrated1", "migrated2");
+        assertThat(rebuild.getMigrationFailures()).containsExactly(failure1, failure2);
+    }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/exceptions/IllegalSchemaExceptionTest.java b/core/tests/coretests/src/android/app/appsearch/external/exceptions/IllegalSchemaExceptionTest.java
new file mode 100644
index 0000000..1774d72
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/exceptions/IllegalSchemaExceptionTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.exceptions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class IllegalSchemaExceptionTest {
+    @Test
+    public void testExceptionWithMessage() {
+        IllegalSchemaException e = new IllegalSchemaException("ERROR MESSAGE");
+        assertThat(e.getMessage()).isEqualTo("ERROR MESSAGE");
+        assertThat(e).isInstanceOf(IllegalArgumentException.class);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
index 180cceb..7e5fd92 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
@@ -113,20 +113,18 @@
 
     /**
      * Animator for OneHanded transition animation which supports both alpha and bounds animation.
-     *
-     * @param <T> Type of property to animate, either offset (float)
      */
     // TODO: Refactoring to use SpringAnimation and DynamicAnimation instead of using ValueAnimator
     //  to implement One-Handed transition animation. (b/185129031)
-    public abstract static class OneHandedTransitionAnimator<T> extends ValueAnimator implements
+    public abstract static class OneHandedTransitionAnimator extends ValueAnimator implements
             ValueAnimator.AnimatorUpdateListener,
             ValueAnimator.AnimatorListener {
 
         private final SurfaceControl mLeash;
         private final WindowContainerToken mToken;
-        private T mStartValue;
-        private T mEndValue;
-        private T mCurrentValue;
+        private float mStartValue;
+        private float mEndValue;
+        private float mCurrentValue;
 
         private final List<OneHandedAnimationCallback> mOneHandedAnimationCallbacks =
                 new ArrayList<>();
@@ -137,7 +135,7 @@
         private @TransitionDirection int mTransitionDirection;
 
         private OneHandedTransitionAnimator(WindowContainerToken token, SurfaceControl leash,
-                T startValue, T endValue) {
+                float startValue, float endValue) {
             mLeash = leash;
             mToken = token;
             mStartValue = startValue;
@@ -204,7 +202,7 @@
             mSurfaceTransactionHelper = helper;
         }
 
-        OneHandedTransitionAnimator<T> addOneHandedAnimationCallback(
+        OneHandedTransitionAnimator addOneHandedAnimationCallback(
                 OneHandedAnimationCallback callback) {
             mOneHandedAnimationCallbacks.add(callback);
             return this;
@@ -223,27 +221,27 @@
             return mTransitionDirection;
         }
 
-        OneHandedTransitionAnimator<T> setTransitionDirection(int direction) {
+        OneHandedTransitionAnimator setTransitionDirection(int direction) {
             mTransitionDirection = direction;
             return this;
         }
 
-        T getStartValue() {
+        float getStartValue() {
             return mStartValue;
         }
 
-        T getEndValue() {
+        float getEndValue() {
             return mEndValue;
         }
 
-        void setCurrentValue(T value) {
+        void setCurrentValue(float value) {
             mCurrentValue = value;
         }
 
         /**
          * Updates the {@link #mEndValue}.
          */
-        void updateEndValue(T endValue) {
+        void updateEndValue(float endValue) {
             mEndValue = endValue;
         }
 
@@ -252,10 +250,10 @@
         }
 
         @VisibleForTesting
-        static OneHandedTransitionAnimator<Float> ofYOffset(WindowContainerToken token,
+        static OneHandedTransitionAnimator ofYOffset(WindowContainerToken token,
                 SurfaceControl leash, float startValue, float endValue, Rect displayBounds) {
 
-            return new OneHandedTransitionAnimator<Float>(token, leash, startValue, endValue) {
+            return new OneHandedTransitionAnimator(token, leash, startValue, endValue) {
 
                 private final Rect mTmpRect = new Rect(displayBounds);
 
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 7e45f95..7d061fb 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -221,6 +221,16 @@
     for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
       iter2->dynamic_ref_table->addMapping(String16(package_name.c_str(), package_name.size()),
                                            iter->dynamic_ref_table->mAssignedPackageId);
+
+      // Add the alias resources to the dynamic reference table of every package group. Since
+      // staging aliases can only be defined by the framework package (which is not a shared
+      // library), the compile-time package id of the framework is the same across all packages
+      // that compile against the framework.
+      for (const auto& package : iter2->packages_) {
+        for (const auto& entry : package.loaded_package_->GetAliasResourceIdMap()) {
+          iter->dynamic_ref_table->addAlias(entry.first, entry.second);
+        }
+      }
     }
   }
 }
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index cb620cc..d17c328 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -41,6 +41,7 @@
 
 namespace android {
 
+constexpr const static int kFrameworkPackageId = 0x01;
 constexpr const static int kAppPackageId = 0x7f;
 
 namespace {
@@ -675,6 +676,42 @@
         }
       } break;
 
+      case RES_TABLE_STAGED_ALIAS_TYPE: {
+        if (loaded_package->package_id_ != kFrameworkPackageId) {
+          LOG(WARNING) << "Alias chunk ignored for non-framework package '"
+                       << loaded_package->package_name_ << "'";
+          break;
+        }
+
+        std::unordered_set<uint32_t> finalized_ids;
+        const auto lib_alias = child_chunk.header<ResTable_staged_alias_header>();
+        if (!lib_alias) {
+          return {};
+        }
+        const auto entry_begin = child_chunk.data_ptr().convert<ResTable_staged_alias_entry>();
+        const auto entry_end = entry_begin + dtohl(lib_alias->count);
+        for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
+          if (!entry_iter) {
+            return {};
+          }
+          auto finalized_id = dtohl(entry_iter->finalizedResId);
+          if (!finalized_ids.insert(finalized_id).second) {
+            LOG(ERROR) << StringPrintf("Repeated finalized resource id '%08x' in staged aliases.",
+                                       finalized_id);
+            return {};
+          }
+
+          auto staged_id = dtohl(entry_iter->stagedResId);
+          auto [_, success] = loaded_package->alias_id_map_.insert(std::make_pair(staged_id,
+                                                                                  finalized_id));
+          if (!success) {
+            LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.",
+                                       staged_id);
+            return {};
+          }
+        }
+      } break;
+
       default:
         LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type());
         break;
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 30500ab..cae2d0b 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -7079,6 +7079,10 @@
     mLookupTable[buildPackageId] = runtimePackageId;
 }
 
+void DynamicRefTable::addAlias(uint32_t stagedId, uint32_t finalizedId) {
+  mAliasId[stagedId] = finalizedId;
+}
+
 status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
     uint32_t res = *resId;
     size_t packageId = Res_GETPACKAGE(res) + 1;
@@ -7088,8 +7092,16 @@
         return NO_ERROR;
     }
 
-    if (packageId == APP_PACKAGE_ID && !mAppAsLib) {
-        // No lookup needs to be done, app package IDs are absolute.
+    auto alias_id = mAliasId.find(res);
+    if (alias_id != mAliasId.end()) {
+      // Rewrite the resource id to its alias resource id. Since the alias resource id is a
+      // compile-time id, it still needs to be resolved further.
+      res = alias_id->second;
+    }
+
+    if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) {
+        // No lookup needs to be done, app and framework package IDs are absolute.
+        *resId = res;
         return NO_ERROR;
     }
 
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 3b222c5..9bbdede 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -17,6 +17,7 @@
 #ifndef LOADEDARSC_H_
 #define LOADEDARSC_H_
 
+#include <map>
 #include <memory>
 #include <set>
 #include <vector>
@@ -171,51 +172,51 @@
       incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
 
   // Returns the string pool where type names are stored.
-  inline const ResStringPool* GetTypeStringPool() const {
+  const ResStringPool* GetTypeStringPool() const {
     return &type_string_pool_;
   }
 
   // Returns the string pool where the names of resource entries are stored.
-  inline const ResStringPool* GetKeyStringPool() const {
+  const ResStringPool* GetKeyStringPool() const {
     return &key_string_pool_;
   }
 
-  inline const std::string& GetPackageName() const {
+  const std::string& GetPackageName() const {
     return package_name_;
   }
 
-  inline int GetPackageId() const {
+  int GetPackageId() const {
     return package_id_;
   }
 
   // Returns true if this package is dynamic (shared library) and needs to have an ID assigned.
-  inline bool IsDynamic() const {
+  bool IsDynamic() const {
     return (property_flags_ & PROPERTY_DYNAMIC) != 0;
   }
 
   // Returns true if this package is a Runtime Resource Overlay.
-  inline bool IsOverlay() const {
+  bool IsOverlay() const {
     return (property_flags_ & PROPERTY_OVERLAY) != 0;
   }
 
   // Returns true if this package originates from a system provided resource.
-  inline bool IsSystem() const {
+  bool IsSystem() const {
     return (property_flags_ & PROPERTY_SYSTEM) != 0;
   }
 
   // Returns true if this package is a custom loader and should behave like an overlay.
-  inline bool IsCustomLoader() const {
+  bool IsCustomLoader() const {
     return (property_flags_ & PROPERTY_LOADER) != 0;
   }
 
-  inline package_property_t GetPropertyFlags() const {
+  package_property_t GetPropertyFlags() const {
     return property_flags_;
   }
 
   // Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
   // package could have been assigned a different package ID than what this LoadedPackage was
   // compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime.
-  inline const std::vector<DynamicPackageEntry>& GetDynamicPackageMap() const {
+  const std::vector<DynamicPackageEntry>& GetDynamicPackageMap() const {
     return dynamic_package_map_;
   }
 
@@ -270,6 +271,10 @@
     return overlayable_map_;
   }
 
+  const std::map<uint32_t, uint32_t>& GetAliasResourceIdMap() const {
+    return alias_id_map_;
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
 
@@ -287,6 +292,7 @@
   ByteBucketArray<uint32_t> resource_ids_;
   std::vector<DynamicPackageEntry> dynamic_package_map_;
   std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
+  std::map<uint32_t, uint32_t> alias_id_map_;
 
   // A map of overlayable name to actor
   std::unordered_map<std::string, std::string> overlayable_map_;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 17c1404..3d66244 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -39,6 +39,7 @@
 #include <android/configuration.h>
 
 #include <array>
+#include <map>
 #include <memory>
 
 namespace android {
@@ -229,30 +230,31 @@
 };
 
 enum {
-    RES_NULL_TYPE               = 0x0000,
-    RES_STRING_POOL_TYPE        = 0x0001,
-    RES_TABLE_TYPE              = 0x0002,
-    RES_XML_TYPE                = 0x0003,
+    RES_NULL_TYPE                     = 0x0000,
+    RES_STRING_POOL_TYPE              = 0x0001,
+    RES_TABLE_TYPE                    = 0x0002,
+    RES_XML_TYPE                      = 0x0003,
 
     // Chunk types in RES_XML_TYPE
-    RES_XML_FIRST_CHUNK_TYPE    = 0x0100,
-    RES_XML_START_NAMESPACE_TYPE= 0x0100,
-    RES_XML_END_NAMESPACE_TYPE  = 0x0101,
-    RES_XML_START_ELEMENT_TYPE  = 0x0102,
-    RES_XML_END_ELEMENT_TYPE    = 0x0103,
-    RES_XML_CDATA_TYPE          = 0x0104,
-    RES_XML_LAST_CHUNK_TYPE     = 0x017f,
+    RES_XML_FIRST_CHUNK_TYPE          = 0x0100,
+    RES_XML_START_NAMESPACE_TYPE      = 0x0100,
+    RES_XML_END_NAMESPACE_TYPE        = 0x0101,
+    RES_XML_START_ELEMENT_TYPE        = 0x0102,
+    RES_XML_END_ELEMENT_TYPE          = 0x0103,
+    RES_XML_CDATA_TYPE                = 0x0104,
+    RES_XML_LAST_CHUNK_TYPE           = 0x017f,
     // This contains a uint32_t array mapping strings in the string
     // pool back to resource identifiers.  It is optional.
-    RES_XML_RESOURCE_MAP_TYPE   = 0x0180,
+    RES_XML_RESOURCE_MAP_TYPE         = 0x0180,
 
     // Chunk types in RES_TABLE_TYPE
-    RES_TABLE_PACKAGE_TYPE      = 0x0200,
-    RES_TABLE_TYPE_TYPE         = 0x0201,
-    RES_TABLE_TYPE_SPEC_TYPE    = 0x0202,
-    RES_TABLE_LIBRARY_TYPE      = 0x0203,
-    RES_TABLE_OVERLAYABLE_TYPE  = 0x0204,
+    RES_TABLE_PACKAGE_TYPE            = 0x0200,
+    RES_TABLE_TYPE_TYPE               = 0x0201,
+    RES_TABLE_TYPE_SPEC_TYPE          = 0x0202,
+    RES_TABLE_LIBRARY_TYPE            = 0x0203,
+    RES_TABLE_OVERLAYABLE_TYPE        = 0x0204,
     RES_TABLE_OVERLAYABLE_POLICY_TYPE = 0x0205,
+    RES_TABLE_STAGED_ALIAS_TYPE       = 0x0206,
 };
 
 /**
@@ -1639,6 +1641,29 @@
 };
 
 /**
+ * A map that allows rewriting staged (non-finalized) resource ids to their finalized counterparts.
+ */
+struct ResTable_staged_alias_header
+{
+    struct ResChunk_header header;
+
+    // The number of ResTable_staged_alias_entry that follow this header.
+    uint32_t count;
+};
+
+/**
+ * Maps the staged (non-finalized) resource id to its finalized resource id.
+ */
+struct ResTable_staged_alias_entry
+{
+  // The compile-time staged resource id to rewrite.
+  uint32_t stagedResId;
+
+  // The compile-time finalized resource id to which the staged resource id should be rewritten.
+  uint32_t finalizedResId;
+};
+
+/**
  * Specifies the set of resources that are explicitly allowed to be overlaid by RROs.
  */
 struct ResTable_overlayable_header
@@ -1751,6 +1776,8 @@
 
     void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId);
 
+    void addAlias(uint32_t stagedId, uint32_t finalizedId);
+
     // Returns whether or not the value must be looked up.
     bool requiresLookup(const Res_value* value) const;
 
@@ -1768,6 +1795,7 @@
     uint8_t                         mLookupTable[256];
     KeyedVector<String16, uint8_t>  mEntries;
     bool                            mAppAsLib;
+    std::map<uint32_t, uint32_t>    mAliasId;
 };
 
 bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 0212309..c6ab8a2 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -583,6 +583,7 @@
         "libhwui_defaults",
         "android_graphics_apex",
         "android_graphics_jni",
+        "linker_hugepage_aligned",
     ],
     export_header_lib_headers: ["android_graphics_apex_headers"],
     target: {
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index c58c888..9ed801b 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -84,7 +84,7 @@
 bool Properties::useHintManager = true;
 int Properties::targetCpuTimePercentage = 70;
 
-StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::Shader;
+StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
 
 bool Properties::load() {
     bool prevDebugLayersUpdates = debugLayersUpdates;
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 82ce5c6..9d2b617 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -200,8 +200,9 @@
 enum class RenderPipelineType { SkiaGL, SkiaVulkan, NotInitialized = 128 };
 
 enum class StretchEffectBehavior {
-    Shader,
-    LinearScale,
+    ShaderHWUI, // Stretch shader in HWUI only, matrix scale in SF
+    Shader, // Stretch shader in both HWUI and SF
+    LinearScale // Linear stretch everywhere
 };
 
 /**
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 6123c05..cd0783f 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -573,8 +573,9 @@
             const RenderProperties& props = node.properties();
 
             uirenderer::Rect bounds(props.getWidth(), props.getHeight());
-            if (Properties::stretchEffectBehavior == StretchEffectBehavior::Shader &&
-                info.stretchEffectCount) {
+            bool useStretchShader = Properties::stretchEffectBehavior !=
+                StretchEffectBehavior::LinearScale;
+            if (useStretchShader && info.stretchEffectCount) {
                 handleStretchEffect(info, bounds);
             }
 
@@ -679,30 +680,32 @@
             stretchTargetBounds(*effect, result.width, result.height,
                                 childRelativeBounds,targetBounds);
 
-            JNIEnv* env = jnienv();
+            if (Properties::stretchEffectBehavior == StretchEffectBehavior::Shader) {
+                JNIEnv* env = jnienv();
 
-            jobject localref = env->NewLocalRef(mWeakRef);
-            if (CC_UNLIKELY(!localref)) {
-                env->DeleteWeakGlobalRef(mWeakRef);
-                mWeakRef = nullptr;
-                return;
-            }
+                jobject localref = env->NewLocalRef(mWeakRef);
+                if (CC_UNLIKELY(!localref)) {
+                    env->DeleteWeakGlobalRef(mWeakRef);
+                    mWeakRef = nullptr;
+                    return;
+                }
 #ifdef __ANDROID__  // Layoutlib does not support CanvasContext
-            SkVector stretchDirection = effect->getStretchDirection();
-            env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod,
-                                info.canvasContext.getFrameNumber(),
-                                result.width,
-                                result.height,
-                                stretchDirection.fX,
-                                stretchDirection.fY,
-                                effect->maxStretchAmountX,
-                                effect->maxStretchAmountY,
-                                childRelativeBounds.left(),
-                                childRelativeBounds.top(),
-                                childRelativeBounds.right(),
-                                childRelativeBounds.bottom());
+                SkVector stretchDirection = effect->getStretchDirection();
+                env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod,
+                                    info.canvasContext.getFrameNumber(),
+                                    result.width,
+                                    result.height,
+                                    stretchDirection.fX,
+                                    stretchDirection.fY,
+                                    effect->maxStretchAmountX,
+                                    effect->maxStretchAmountY,
+                                    childRelativeBounds.left(),
+                                    childRelativeBounds.top(),
+                                    childRelativeBounds.right(),
+                                    childRelativeBounds.bottom());
 #endif
-            env->DeleteLocalRef(localref);
+                env->DeleteLocalRef(localref);
+            }
         }
 
         void doUpdatePositionAsync(jlong frameNumber, jint left, jint top,
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 509884e..57cdde2 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -172,7 +172,7 @@
 }
 
 static bool stretchNeedsLayer(const LayerProperties& properties) {
-    return Properties::stretchEffectBehavior == StretchEffectBehavior::Shader &&
+    return Properties::stretchEffectBehavior != StretchEffectBehavior::LinearScale &&
            !properties.getStretchEffect().isEmpty();
 }
 
@@ -253,7 +253,7 @@
 
             const StretchEffect& stretch = properties.layerProperties().getStretchEffect();
             if (stretch.isEmpty() ||
-                Properties::stretchEffectBehavior != StretchEffectBehavior::Shader) {
+                Properties::stretchEffectBehavior == StretchEffectBehavior::LinearScale) {
                 // If we don't have any stretch effects, issue the filtered
                 // canvas draw calls to make sure we still punch a hole
                 // with the same canvas transformation + clip into the target
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f2f9a26..14d4937 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7068,15 +7068,7 @@
 
     /**
      * Set a certain surround format as enabled or not.
-     * @param audioFormat a surround format, the value is one of
-     *        {@link AudioFormat#ENCODING_AC3}, {@link AudioFormat#ENCODING_E_AC3},
-     *        {@link AudioFormat#ENCODING_DTS}, {@link AudioFormat#ENCODING_DTS_HD},
-     *        {@link AudioFormat#ENCODING_AAC_LC}, {@link AudioFormat#ENCODING_DOLBY_TRUEHD},
-     *        {@link AudioFormat#ENCODING_E_AC3_JOC}. Once {@link AudioFormat#ENCODING_AAC_LC} is
-     *        set as enabled, {@link AudioFormat#ENCODING_AAC_LC},
-     *        {@link AudioFormat#ENCODING_AAC_HE_V1}, {@link AudioFormat#ENCODING_AAC_HE_V2},
-     *        {@link AudioFormat#ENCODING_AAC_ELD}, {@link AudioFormat#ENCODING_AAC_XHE} are
-     *        all enabled.
+     *
      * @param enabled the required surround format state, true for enabled, false for disabled
      * @return true if successful, otherwise false
      */
diff --git a/media/java/android/media/metrics/PlaybackErrorEvent.java b/media/java/android/media/metrics/PlaybackErrorEvent.java
index d155576..184b359 100644
--- a/media/java/android/media/metrics/PlaybackErrorEvent.java
+++ b/media/java/android/media/metrics/PlaybackErrorEvent.java
@@ -39,71 +39,95 @@
     /** Error code for runtime errors */
     public static final int ERROR_RUNTIME = 2;
 
-    /** No network */
-    public static final int ERROR_NETWORK_OFFLINE = 3;
-    /** Connection opening error */
-    public static final int ERROR_NETWORK_CONNECT = 4;
-    /** Bad HTTP status code */
-    public static final int ERROR_NETWORK_BAD_STATUS = 5;
-    /** DNS resolution error */
-    public static final int ERROR_NETWORK_DNS = 6;
-    /** Network socket timeout */
-    public static final int ERROR_NETWORK_TIMEOUT = 7;
-    /** Connection closed */
-    public static final int ERROR_NETWORK_CLOSED = 8;
-    /** Other network errors */
-    public static final int ERROR_NETWORK_OTHER = 9;
-
-    /** Manifest parsing error */
-    public static final int ERROR_MEDIA_MANIFEST = 10;
+    /** Error code for lack of network connectivity while trying to access a network resource */
+    public static final int ERROR_IO_NETWORK_UNAVAILABLE = 3;
+    /** Error code for a failure while establishing a network connection */
+    public static final int ERROR_IO_NETWORK_CONNECTION_FAILED = 4;
+    /** Error code for an HTTP server returning an unexpected HTTP response status code */
+    public static final int ERROR_IO_BAD_HTTP_STATUS = 5;
+    /** Error code for failing to resolve a hostname */
+    public static final int ERROR_IO_DNS_FAILED = 6;
     /**
-     * Media bitstream (audio, video, text, metadata) parsing error, either malformed or
-     * unsupported.
+     * Error code for a network timeout, meaning the server is taking too long to fulfill
+     * a request
      */
-    public static final int ERROR_MEDIA_PARSER = 11;
-    /** Other media errors */
-    public static final int ERROR_MEDIA_OTHER = 12;
+    public static final int ERROR_IO_CONNECTION_TIMEOUT = 7;
+    /** Error code for an existing network connection being unexpectedly closed */
+    public static final int ERROR_IO_CONNECTION_CLOSED = 8;
+    /** Error code for other Input/Output errors */
+    public static final int ERROR_IO_OTHER = 9;
 
-    /** Codec initialization failed */
-    public static final int ERROR_DECODER_INIT = 13;
-    /** Decoding failed */
-    public static final int ERROR_DECODER_DECODE = 14;
-    /** Out of memory */
-    public static final int ERROR_DECODER_OOM = 15;
-    /** Other decoder errors */
-    public static final int ERROR_DECODER_OTHER = 16;
+    /** Error code for a parsing error associated to a media manifest */
+    public static final int ERROR_PARSING_MANIFEST_MALFORMED = 10;
+    /** Error code for a parsing error associated to a media container format bitstream */
+    public static final int ERROR_PARSING_CONTAINER_MALFORMED = 11;
+    /** Error code for other media parsing errors */
+    public static final int ERROR_PARSING_OTHER = 12;
 
-    /** AudioTrack initialization failed */
-    public static final int ERROR_AUDIOTRACK_INIT = 17;
-    /** AudioTrack writing failed */
-    public static final int ERROR_AUDIOTRACK_WRITE = 18;
-    /** Other AudioTrack errors */
-    public static final int ERROR_AUDIOTRACK_OTHER = 19;
+    /** Error code for a decoder initialization failure */
+    public static final int ERROR_DECODER_INIT_FAILED = 13;
+    /** Error code for a failure while trying to decode media samples */
+    public static final int ERROR_DECODING_FAILED = 14;
+    /**
+     * Error code for trying to decode content whose format exceeds the capabilities of the device.
+     */
+    public static final int ERROR_DECODING_FORMAT_EXCEEDS_CAPABILITIES = 15;
+    /** Error code for other decoding errors */
+    public static final int ERROR_DECODING_OTHER = 16;
 
-    /** Exception in remote controller or player */
+    /** Error code for an AudioTrack initialization failure */
+    public static final int ERROR_AUDIO_TRACK_INIT_FAILED = 17;
+    /** Error code for an AudioTrack write operation failure */
+    public static final int ERROR_AUDIO_TRACK_WRITE_FAILED = 18;
+    /** Error code for other AudioTrack errors */
+    public static final int ERROR_AUDIO_TRACK_OTHER = 19;
+
+    /** Error code for an unidentified error in a remote controller or player */
     public static final int ERROR_PLAYER_REMOTE = 20;
-    /** Error when a Live playback falls behind the Live DVR window. */
+    /**
+     * Error code for the loading position falling behind the sliding window of available live
+     * content.
+     */
     public static final int ERROR_PLAYER_BEHIND_LIVE_WINDOW = 21;
-    /** Other player errors */
+    /** Error code for other player errors */
     public static final int ERROR_PLAYER_OTHER = 22;
 
-    /** Scheme unsupported by device */
-    public static final int ERROR_DRM_UNAVAILABLE = 23;
-    /** Provisioning failed */
+    /** Error code for a chosen DRM protection scheme not being supported by the device */
+    public static final int ERROR_DRM_SCHEME_UNSUPPORTED = 23;
+    /** Error code for a failure while provisioning the device */
     public static final int ERROR_DRM_PROVISIONING_FAILED = 24;
-    /** Failed to acquire license */
-    public static final int ERROR_DRM_LICENSE_ERROR = 25;
-    /** Operation prevented by license policy */
-    public static final int ERROR_DRM_DISALLOWED = 26;
-    /** Failure in the DRM system */
+    /** Error code for a failure while trying to obtain a license */
+    public static final int ERROR_DRM_LICENSE_ACQUISITION_FAILED = 25;
+    /** Error code an operation being disallowed by a license policy */
+    public static final int ERROR_DRM_DISALLOWED_OPERATION = 26;
+    /** Error code for an error in the DRM system */
     public static final int ERROR_DRM_SYSTEM_ERROR = 27;
-    /** Incompatible content */
+    /** Error code for attempting to play incompatible DRM-protected content */
     public static final int ERROR_DRM_CONTENT_ERROR = 28;
-    /** Device has been revoked */
-    public static final int ERROR_DRM_REVOKED = 29;
-    /** Other drm errors */
+    /** Error code for the device having revoked DRM privileges */
+    public static final int ERROR_DRM_DEVICE_REVOKED = 29;
+    /** Error code for other DRM errors */
     public static final int ERROR_DRM_OTHER = 30;
 
+    /** Error code for a non-existent file */
+    public static final int ERROR_IO_FILE_NOT_FOUND = 31;
+    /**
+     * Error code for lack of permission to perform an IO operation, for example, lack of permission
+     * to access internet or external storage.
+     */
+    public static final int ERROR_IO_NO_PERMISSION = 32;
+
+    /** Error code for an unsupported feature in a media manifest */
+    public static final int ERROR_PARSING_MANIFEST_UNSUPPORTED = 33;
+    /**
+     * Error code for attempting to extract a file with an unsupported media container format, or an
+     * unsupported media container feature
+     */
+    public static final int ERROR_PARSING_CONTAINER_UNSUPPORTED = 34;
+
+    /** Error code for trying to decode content whose format is not supported */
+    public static final int ERROR_DECODING_FORMAT_UNSUPPORTED = 35;
+
 
     private final @Nullable String mExceptionStack;
     private final int mErrorCode;
@@ -116,34 +140,39 @@
         ERROR_UNKNOWN,
         ERROR_OTHER,
         ERROR_RUNTIME,
-        ERROR_NETWORK_OFFLINE,
-        ERROR_NETWORK_CONNECT,
-        ERROR_NETWORK_BAD_STATUS,
-        ERROR_NETWORK_DNS,
-        ERROR_NETWORK_TIMEOUT,
-        ERROR_NETWORK_CLOSED,
-        ERROR_NETWORK_OTHER,
-        ERROR_MEDIA_MANIFEST,
-        ERROR_MEDIA_PARSER,
-        ERROR_MEDIA_OTHER,
-        ERROR_DECODER_INIT,
-        ERROR_DECODER_DECODE,
-        ERROR_DECODER_OOM,
-        ERROR_DECODER_OTHER,
-        ERROR_AUDIOTRACK_INIT,
-        ERROR_AUDIOTRACK_WRITE,
-        ERROR_AUDIOTRACK_OTHER,
+        ERROR_IO_NETWORK_UNAVAILABLE,
+        ERROR_IO_NETWORK_CONNECTION_FAILED,
+        ERROR_IO_BAD_HTTP_STATUS,
+        ERROR_IO_DNS_FAILED,
+        ERROR_IO_CONNECTION_TIMEOUT,
+        ERROR_IO_CONNECTION_CLOSED,
+        ERROR_IO_OTHER,
+        ERROR_PARSING_MANIFEST_MALFORMED,
+        ERROR_PARSING_CONTAINER_MALFORMED,
+        ERROR_PARSING_OTHER,
+        ERROR_DECODER_INIT_FAILED,
+        ERROR_DECODING_FAILED,
+        ERROR_DECODING_FORMAT_EXCEEDS_CAPABILITIES,
+        ERROR_DECODING_OTHER,
+        ERROR_AUDIO_TRACK_INIT_FAILED,
+        ERROR_AUDIO_TRACK_WRITE_FAILED,
+        ERROR_AUDIO_TRACK_OTHER,
         ERROR_PLAYER_REMOTE,
         ERROR_PLAYER_BEHIND_LIVE_WINDOW,
         ERROR_PLAYER_OTHER,
-        ERROR_DRM_UNAVAILABLE,
+        ERROR_DRM_SCHEME_UNSUPPORTED,
         ERROR_DRM_PROVISIONING_FAILED,
-        ERROR_DRM_LICENSE_ERROR,
-        ERROR_DRM_DISALLOWED,
+        ERROR_DRM_LICENSE_ACQUISITION_FAILED,
+        ERROR_DRM_DISALLOWED_OPERATION,
         ERROR_DRM_SYSTEM_ERROR,
         ERROR_DRM_CONTENT_ERROR,
-        ERROR_DRM_REVOKED,
+        ERROR_DRM_DEVICE_REVOKED,
         ERROR_DRM_OTHER,
+        ERROR_IO_FILE_NOT_FOUND,
+        ERROR_IO_NO_PERMISSION,
+        ERROR_PARSING_MANIFEST_UNSUPPORTED,
+        ERROR_PARSING_CONTAINER_UNSUPPORTED,
+        ERROR_DECODING_FORMAT_UNSUPPORTED,
     })
     @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
     public @interface ErrorCode {}
diff --git a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
index 9bf910b..7478b3e 100644
--- a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
+++ b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
@@ -64,7 +64,7 @@
         filter_code,
     };
 
-    int fd = AFileDescriptor_getFD(env, javaFd);
+    int fd = AFileDescriptor_getFd(env, javaFd);
     if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
         jniThrowExceptionFmt(env, "java/net/SocketException",
                 "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
@@ -74,7 +74,7 @@
 static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
 {
     int optval_ignored = 0;
-    int fd = AFileDescriptor_getFD(env, javaFd);
+    int fd = AFileDescriptor_getFd(env, javaFd);
     if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &optval_ignored, sizeof(optval_ignored)) !=
         0) {
         jniThrowExceptionFmt(env, "java/net/SocketException",
@@ -107,7 +107,7 @@
 
 static jint android_net_utils_bindSocketToNetworkHandle(JNIEnv *env, jobject thiz, jobject javaFd,
                                                   jlong netHandle) {
-    return android_setsocknetwork(netHandle, AFileDescriptor_getFD(env, javaFd));
+    return android_setsocknetwork(netHandle, AFileDescriptor_getFd(env, javaFd));
 }
 
 static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst)
@@ -156,7 +156,7 @@
 }
 
 static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
-    int fd = AFileDescriptor_getFD(env, javaFd);
+    int fd = AFileDescriptor_getFd(env, javaFd);
     int rcode;
     uint8_t buf[MAXPACKETSIZE] = {0};
 
@@ -182,7 +182,7 @@
 }
 
 static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) {
-    int fd = AFileDescriptor_getFD(env, javaFd);
+    int fd = AFileDescriptor_getFd(env, javaFd);
     android_res_cancel(fd);
     jniSetFileDescriptorOfFD(env, javaFd, -1);
 }
@@ -210,7 +210,7 @@
         return NULL;
     }
 
-    int fd = AFileDescriptor_getFD(env, javaFd);
+    int fd = AFileDescriptor_getFd(env, javaFd);
     struct tcp_repair_window trw = {};
     socklen_t size = sizeof(trw);
 
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 0bafd5b..3d6cc68 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -2461,7 +2461,8 @@
          * For example {@code TRANSPORT_WIFI} and {@code TRANSPORT_ETHERNET} added to a
          * {@code NetworkCapabilities} would cause either a Wi-Fi network or an Ethernet network
          * to be selected. This is logically different than
-         * {@code NetworkCapabilities.NET_CAPABILITY_*}.
+         * {@code NetworkCapabilities.NET_CAPABILITY_*}. Also note that multiple networks with the
+         * same transport type may be active concurrently.
          *
          * @param transportType the transport type to be added or removed.
          * @return this builder
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 0210079..9c6113c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -86,7 +86,7 @@
     private int mOriginatingUid = PackageInstaller.SessionParams.UID_UNKNOWN;
     private String mOriginatingPackage; // The package name corresponding to #mOriginatingUid
 
-    private boolean localLOGV = false;
+    private final boolean mLocalLOGV = false;
     PackageManager mPm;
     IPackageManager mIpm;
     AppOpsManager mAppOpsManager;
@@ -104,7 +104,7 @@
     private List<UnknownSourcesListener> mActiveUnknownSourcesListeners = new ArrayList<>(1);
 
     // ApplicationInfo object primarily used for already existing applications
-    private ApplicationInfo mAppInfo = null;
+    private ApplicationInfo mAppInfo;
 
     // Buttons to indicate user acceptance
     private Button mOk;
@@ -154,6 +154,7 @@
      * @param id The dialog type to add
      */
     private void showDialogInner(int id) {
+        if (mLocalLOGV) Log.i(TAG, "showDialogInner(" + id + ")");
         DialogFragment currentDialog =
                 (DialogFragment) getFragmentManager().findFragmentByTag("dialog");
         if (currentDialog != null) {
@@ -174,6 +175,7 @@
      * @return The dialog
      */
     private DialogFragment createDialog(int id) {
+        if (mLocalLOGV) Log.i(TAG, "createDialog(" + id + ")");
         switch (id) {
             case DLG_PACKAGE_ERROR:
                 return SimpleErrorDialog.newInstance(R.string.Parse_error_dlg_text);
@@ -294,6 +296,7 @@
 
     @Override
     protected void onCreate(Bundle icicle) {
+        if (mLocalLOGV) Log.i(TAG, "creating for user " + getUserId());
         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
 
         super.onCreate(null);
@@ -354,6 +357,8 @@
         }
 
         boolean wasSetUp = processPackageUri(packageUri);
+        if (mLocalLOGV) Log.i(TAG, "wasSetUp: " + wasSetUp);
+
         if (!wasSetUp) {
             return;
         }
@@ -363,6 +368,8 @@
     protected void onResume() {
         super.onResume();
 
+        if (mLocalLOGV) Log.i(TAG, "onResume(): mAppSnippet=" + mAppSnippet);
+
         if (mAppSnippet != null) {
             // load dummy layout with OK button disabled until we override this layout in
             // startInstallConfirm
@@ -443,15 +450,21 @@
         final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
                 UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
         if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
+            if (mLocalLOGV) Log.i(TAG, "install not allowed: " + UserManager.DISALLOW_INSTALL_APPS);
             showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
             return;
         } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
+            if (mLocalLOGV) {
+                Log.i(TAG, "install not allowed by admin; showing "
+                        + Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
+            }
             startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
             finish();
             return;
         }
 
         if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
+            if (mLocalLOGV) Log.i(TAG, "install allowed");
             initiateInstall();
         } else {
             // Check for unknown sources restrictions.
@@ -462,6 +475,7 @@
             final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
                     & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
             if (systemRestriction != 0) {
+                if (mLocalLOGV) Log.i(TAG, "Showing DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER");
                 showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
             } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
                 startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
@@ -475,13 +489,19 @@
     }
 
     private void startAdminSupportDetailsActivity(String restriction) {
+        if (mLocalLOGV) Log.i(TAG, "startAdminSupportDetailsActivity(): " + restriction);
+
         // If the given restriction is set by an admin, display information about the
         // admin enforcing the restriction for the affected user.
         final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
         final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction);
         if (showAdminSupportDetailsIntent != null) {
+            if (mLocalLOGV) Log.i(TAG, "starting " + showAdminSupportDetailsIntent);
             startActivity(showAdminSupportDetailsIntent);
+        } else {
+            if (mLocalLOGV) Log.w(TAG, "not intent for " + restriction);
         }
+
         finish();
     }
 
@@ -497,6 +517,7 @@
         final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode, mOriginatingUid,
                 mOriginatingPackage, mCallingAttributionTag,
                 "Started package installation activity");
+        if (mLocalLOGV) Log.i(TAG, "handleUnknownSources(): appMode=" + appOpMode);
         switch (appOpMode) {
             case AppOpsManager.MODE_DEFAULT:
                 mAppOpsManager.setMode(appOpCode, mOriginatingUid,
@@ -527,6 +548,7 @@
         mPackageURI = packageUri;
 
         final String scheme = packageUri.getScheme();
+        if (mLocalLOGV) Log.i(TAG, "processPackageUri(): uri=" + packageUri + ", scheme=" + scheme);
 
         switch (scheme) {
             case SCHEME_PACKAGE: {
@@ -543,7 +565,9 @@
                     setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                     return false;
                 }
-                mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
+                CharSequence label = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
+                if (mLocalLOGV) Log.i(TAG, "creating snippet for " + label);
+                mAppSnippet = new PackageUtil.AppSnippet(label,
                         mPm.getApplicationIcon(mPkgInfo.applicationInfo));
             } break;
 
@@ -559,6 +583,7 @@
                     setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                     return false;
                 }
+                if (mLocalLOGV) Log.i(TAG, "creating snippet for local file " + sourceFile);
                 mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
             } break;
 
@@ -604,7 +629,7 @@
             newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
         }
         newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
-        if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
+        if (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI);
         startActivity(newIntent);
         finish();
     }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index d3a9f8f..f5570df 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -107,13 +107,18 @@
                 icon);
     }
 
-    static public class AppSnippet {
+    static final class AppSnippet {
         @NonNull public CharSequence label;
         @Nullable public Drawable icon;
         public AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon) {
             this.label = label;
             this.icon = icon;
         }
+
+        @Override
+        public String toString() {
+            return "AppSnippet[" + label + (icon != null ? "(has" : "(no ") + " icon)]";
+        }
     }
 
     /**
diff --git a/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml b/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
index 8c20e02..ea033a3 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
+++ b/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
@@ -22,8 +22,8 @@
     android:layout_height="wrap_content"
     android:gravity="center_vertical"
     android:orientation="vertical"
-    android:layout_marginStart="16dp"
-    android:layout_marginEnd="16dp"
+    android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
+    android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
     android:paddingBottom="16dp">
 
     <androidx.constraintlayout.widget.ConstraintLayout
@@ -70,6 +70,7 @@
         android:id="@+id/bottom_summary"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
         android:visibility="gone"
         android:ellipsize="marquee"
         android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index b96a4f5..e8da28f 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -118,7 +118,7 @@
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Цуцлах"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Хослуулснаар холбогдсон үед таны харилцагчид болон дуудлагын түүхэд хандах боломжтой."</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>-тай хослуулж чадсангүй."</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Буруу PIN эсхүл дамжих түлхүүрээс шалтгаалан <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-тай хослуулж чадсангүй."</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Буруу ПИН эсхүл дамжих түлхүүрээс шалтгаалан <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-тай хослуулж чадсангүй."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>-тай холбоо барих боломжгүй."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Хослуулахаас <xliff:g id="DEVICE_NAME">%1$s</xliff:g> татгалзсан."</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Компьютер"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index ca185e2..645248c 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -504,7 +504,7 @@
     <string name="cancel" msgid="5665114069455378395">"रद्द गर्नुहोस्"</string>
     <string name="okay" msgid="949938843324579502">"ठिक छ"</string>
     <string name="alarms_and_reminders_label" msgid="6918395649731424294">"अलार्म र रिमाइन्डरहरू"</string>
-    <string name="alarms_and_reminders_switch_title" msgid="4939393911531826222">"अलार्म तथा रिमाइन्डर सेट गर्न दिनुहोस्"</string>
+    <string name="alarms_and_reminders_switch_title" msgid="4939393911531826222">"अलार्म तथा रिमाइन्डर सेट गर्न दिइयोस्"</string>
     <string name="alarms_and_reminders_title" msgid="8819933264635406032">"घडी तथा रिमाइन्डरहरू"</string>
     <string name="alarms_and_reminders_footer_title" product="default" msgid="1122213569699233612">"यो एपलाई अलार्म सेट गर्न तथा अन्य कार्यको समयतालिका तोक्न दिनुहोस्। तपाईंले आफ्नो फोन नचलाएका बेला पनि यो एप प्रयोग गरिन सक्छ। यसले गर्दा थप ब्याट्री खपत हुन सक्छ। यो अनुमति नदिइएका खण्डमा यो एप राम्ररी नचल्न सक्छ र यो एपका अलार्म पनि तोकिएको समयमा बज्ने छैनन्।"</string>
     <string name="alarms_and_reminders_footer_title" product="tablet" msgid="4596201244991057839">"यो एपलाई अलार्म सेट गर्न तथा अन्य कार्यको समयतालिका तोक्न दिनुहोस्। तपाईंले आफ्नो ट्याब्लेट नचलाएका बेला पनि यो एप प्रयोग गरिन सक्छ। यसले गर्दा थप ब्याट्री खपत हुन सक्छ। यो अनुमति नदिइएका खण्डमा यो एप राम्ररी नचल्न सक्छ र यो एपका अलार्म पनि तोकिएको समयमा बज्ने छैनन्।"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 08d87df..404299a 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1147,8 +1147,8 @@
     <string name="battery_info_status_charging_wireless">Charging wirelessly</string>
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
     <string name="battery_info_status_discharging">Not charging</string>
-    <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
-    <string name="battery_info_status_not_charging">Plugged in, can\'t charge right now</string>
+    <!-- Battery Info screen. Value for a status item. A state which device is connected with any charger(e.g. USB, Adapter or Wireless) but not charging yet. Used for diagnostic info screens, precise translation isn't needed -->
+    <string name="battery_info_status_not_charging">Connected, not charging</string>
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
     <string name="battery_info_status_full">Charged</string>
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 941f47f..01d66e6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -34,8 +34,11 @@
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.backup.BackupManager;
+import android.app.compat.CompatChanges;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentProvider;
@@ -350,6 +353,9 @@
     public static String keyToString(int key) {
         return SettingsState.keyToString(key);
     }
+    @ChangeId
+    @EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.S)
+    private static final long ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL = 172670679L;
 
     @Override
     public boolean onCreate() {
@@ -1950,6 +1956,25 @@
             // Skip checking readable annotations for test_only apps
             checkReadableAnnotation(settingsType, settingName);
         }
+        /**
+         * some settings need additional permission check, this is to have a matching security
+         * control from other API alternatives returning the same settings values.
+         * note, the permission enforcement should be based on app's targetSDKlevel to better handle
+         * app-compat.
+         */
+        switch (settingName) {
+            // missing READ_PRIVILEGED_PHONE_STATE permission protection
+            // see alternative API {@link SubscriptionManager#getPreferredDataSubscriptionId()
+            case Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION:
+                // app-compat handling, not break apps targeting on previous SDKs.
+                if (CompatChanges.isChangeEnabled(
+                        ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL)) {
+                    getContext().enforceCallingOrSelfPermission(
+                            Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                            "access global settings MULTI_SIM_DATA_CALL_SUBSCRIPTION");
+                }
+                break;
+        }
         if (!ai.isInstantApp()) {
             return;
         }
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c60ec78..2283844 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -151,7 +151,7 @@
     <dimen name="notification_max_heads_up_height_before_s">162dp</dimen>
 
     <!-- Height of a heads up notification in the status bar -->
-    <dimen name="notification_max_heads_up_height">132dp</dimen>
+    <dimen name="notification_max_heads_up_height">143dp</dimen>
 
     <!-- Height of a heads up notification in the status bar -->
     <dimen name="notification_max_heads_up_height_increased">188dp</dimen>
@@ -1199,7 +1199,7 @@
         0
     </item>
     <!-- Ending text size in sp of batteryLevel for wireless charging animation -->
-    <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">24
+    <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">48
     </item>
     <!-- time until battery info is at full opacity-->
     <integer name="wireless_charging_anim_opacity_offset">80</integer>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 5f27400..5a50f0e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -105,7 +105,7 @@
     @NonNull private final DumpManager mDumpManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
-    @NonNull private final Vibrator mVibrator;
+    @Nullable private final Vibrator mVibrator;
     @NonNull private final Handler mMainHandler;
     @NonNull private final FalsingManager mFalsingManager;
     @NonNull private final PowerManager mPowerManager;
@@ -135,7 +135,8 @@
     private boolean mScreenOn;
     private Runnable mAodInterruptRunnable;
 
-    private static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
+    @VisibleForTesting
+    static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
             new AudioAttributes.Builder()
                     .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                     .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
@@ -144,7 +145,8 @@
     private final VibrationEffect mEffectTick = VibrationEffect.get(VibrationEffect.EFFECT_TICK);
     private final VibrationEffect mEffectTextureTick =
             VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
-    private final VibrationEffect mEffectClick = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+    @VisibleForTesting
+    final VibrationEffect mEffectClick = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
     private final VibrationEffect mEffectHeavy =
             VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
     private final VibrationEffect mDoubleClick =
@@ -152,6 +154,9 @@
     private final Runnable mAcquiredVibration = new Runnable() {
         @Override
         public void run() {
+            if (mVibrator == null) {
+                return;
+            }
             String effect = Settings.Global.getString(mContext.getContentResolver(),
                     "udfps_acquired_type");
             mVibrator.vibrate(getVibration(effect, mEffectTick), VIBRATION_SONIFICATION_ATTRIBUTES);
@@ -389,24 +394,28 @@
                                     PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
 
                             // TODO: this should eventually be removed after ux testing
-                            final ContentResolver contentResolver = mContext.getContentResolver();
-                            int startEnabled = Settings.Global.getInt(contentResolver,
-                                    "udfps_start", 0);
-                            if (startEnabled > 0) {
-                                String startEffectSetting = Settings.Global.getString(
-                                        contentResolver, "udfps_start_type");
-                                mVibrator.vibrate(getVibration(startEffectSetting, mEffectClick),
-                                        VIBRATION_SONIFICATION_ATTRIBUTES);
+                            if (mVibrator != null) {
+                                final ContentResolver contentResolver =
+                                        mContext.getContentResolver();
+                                int startEnabled = Settings.Global.getInt(contentResolver,
+                                        "udfps_start", 1);
+                                if (startEnabled > 0) {
+                                    String startEffectSetting = Settings.Global.getString(
+                                            contentResolver, "udfps_start_type");
+                                    mVibrator.vibrate(getVibration(startEffectSetting,
+                                            mEffectClick), VIBRATION_SONIFICATION_ATTRIBUTES);
+                                }
+
+                                int acquiredEnabled = Settings.Global.getInt(contentResolver,
+                                        "udfps_acquired", 0);
+                                if (acquiredEnabled > 0) {
+                                    int delay = Settings.Global.getInt(contentResolver,
+                                            "udfps_acquired_delay", 500);
+                                    mMainHandler.removeCallbacks(mAcquiredVibration);
+                                    mMainHandler.postDelayed(mAcquiredVibration, delay);
+                                }
                             }
 
-                            int acquiredEnabled = Settings.Global.getInt(contentResolver,
-                                    "udfps_acquired", 0);
-                            if (acquiredEnabled > 0) {
-                                int delay = Settings.Global.getInt(contentResolver,
-                                        "udfps_acquired_delay", 500);
-                                mMainHandler.removeCallbacks(mAcquiredVibration);
-                                mMainHandler.postDelayed(mAcquiredVibration, delay);
-                            }
                             handled = true;
                         } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
                             Log.v(TAG, "onTouch | finger move: " + touchInfo);
@@ -456,11 +465,12 @@
             @NonNull FalsingManager falsingManager,
             @NonNull PowerManager powerManager,
             @NonNull AccessibilityManager accessibilityManager,
-            @NonNull ScreenLifecycle screenLifecycle) {
+            @NonNull ScreenLifecycle screenLifecycle,
+            @Nullable Vibrator vibrator) {
         mContext = context;
         // TODO (b/185124905): inject main handler and vibrator once done prototyping
         mMainHandler = new Handler(Looper.getMainLooper());
-        mVibrator = context.getSystemService(Vibrator.class);
+        mVibrator = vibrator;
         mInflater = inflater;
         // The fingerprint manager is queried for UDFPS before this class is constructed, so the
         // fingerprint manager should never be null.
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index 9d47bbb..f01ac68 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -34,7 +34,7 @@
  */
 public class WirelessChargingAnimation {
 
-    public static final long DURATION = 1133;
+    public static final long DURATION = 1500;
     private static final String TAG = "WirelessChargingView";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 38ffec1..0d3e2ae 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -20,6 +20,7 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.graphics.Color;
 import android.graphics.PointF;
 import android.util.AttributeSet;
 import android.util.TypedValue;
@@ -42,7 +43,9 @@
  */
 public class WirelessChargingLayout extends FrameLayout {
     public static final int UNKNOWN_BATTERY_LEVEL = -1;
-    private static final long RIPPLE_ANIMATION_DURATION = 1133;
+    private static final long RIPPLE_ANIMATION_DURATION = 1500;
+    private static final int SCRIM_COLOR = 0x4C000000;
+    private static final int SCRIM_FADE_DURATION = 300;
     private ChargingRippleView mRippleView;
 
     public WirelessChargingLayout(Context context) {
@@ -121,6 +124,19 @@
         AnimatorSet animatorSet = new AnimatorSet();
         animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator);
 
+        ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this,
+                "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR);
+        scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION);
+        scrimFadeInAnimator.setInterpolator(Interpolators.LINEAR);
+        ValueAnimator scrimFadeOutAnimator = ObjectAnimator.ofArgb(this,
+                "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT);
+        scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION);
+        scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR);
+        scrimFadeOutAnimator.setStartDelay(RIPPLE_ANIMATION_DURATION - SCRIM_FADE_DURATION);
+        AnimatorSet animatorSetScrim = new AnimatorSet();
+        animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator);
+        animatorSetScrim.start();
+
         mRippleView = findViewById(R.id.wireless_charging_ripple);
         OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 04e32a1..3a6f1d5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -190,6 +190,16 @@
                 || vpnName != null || vpnNameWorkProfile != null
                 || isProfileOwnerOfOrganizationOwnedDevice || isParentalControlsEnabled
                 || (hasWorkProfile && isNetworkLoggingEnabled);
+        // Update the view to be untappable if the device is an organization-owned device with a
+        // managed profile and there is no policy set which requires a privacy disclosure.
+        if (mIsVisible && isProfileOwnerOfOrganizationOwnedDevice && !isNetworkLoggingEnabled
+                && !hasCACertsInWorkProfile && vpnNameWorkProfile == null) {
+            mRootView.setClickable(false);
+            mRootView.findViewById(R.id.footer_icon).setVisibility(View.GONE);
+        } else {
+            mRootView.setClickable(true);
+            mRootView.findViewById(R.id.footer_icon).setVisibility(View.VISIBLE);
+        }
         // Update the string
         mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile,
                 hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName,
@@ -345,20 +355,15 @@
 
     private View createOrganizationDialogView() {
         final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
-        boolean isProfileOwnerOfOrganizationOwnedDevice =
-                mSecurityController.isProfileOwnerOfOrganizationOwnedDevice();
         final boolean hasWorkProfile = mSecurityController.hasWorkProfile();
         final CharSequence deviceOwnerOrganization =
                 mSecurityController.getDeviceOwnerOrganizationName();
-        final CharSequence workProfileOrganizationName =
-                mSecurityController.getWorkProfileOrganizationName();
         final boolean hasCACerts = mSecurityController.hasCACertInCurrentUser();
         final boolean hasCACertsInWorkProfile = mSecurityController.hasCACertInWorkProfile();
         final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled();
         final String vpnName = mSecurityController.getPrimaryVpnName();
         final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName();
 
-
         View dialogView = LayoutInflater.from(mContext)
                 .inflate(R.layout.quick_settings_footer_dialog, null, false);
 
@@ -368,8 +373,7 @@
         deviceManagementSubtitle.setText(getManagementTitle(deviceOwnerOrganization));
 
         CharSequence managementMessage = getManagementMessage(isDeviceManaged,
-                deviceOwnerOrganization, isProfileOwnerOfOrganizationOwnedDevice,
-                workProfileOrganizationName);
+                deviceOwnerOrganization);
         if (managementMessage == null) {
             dialogView.findViewById(R.id.device_management_disclosures).setVisibility(View.GONE);
         } else {
@@ -377,11 +381,7 @@
             TextView deviceManagementWarning =
                     (TextView) dialogView.findViewById(R.id.device_management_warning);
             deviceManagementWarning.setText(managementMessage);
-            // Don't show the policies button for profile owner of org owned device, because there
-            // is no policies settings screen for it
-            if (!isProfileOwnerOfOrganizationOwnedDevice) {
-                mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this);
-            }
+            mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this);
         }
 
         // ca certificate section
@@ -496,12 +496,11 @@
     }
 
     protected CharSequence getManagementMessage(boolean isDeviceManaged,
-            CharSequence organizationName, boolean isProfileOwnerOfOrganizationOwnedDevice,
-            CharSequence workProfileOrganizationName) {
-        if (!isDeviceManaged && !isProfileOwnerOfOrganizationOwnedDevice) {
+            CharSequence organizationName) {
+        if (!isDeviceManaged) {
             return null;
         }
-        if (isDeviceManaged && organizationName != null) {
+        if (organizationName != null) {
             if (isFinancedDevice()) {
                 return mContext.getString(R.string.monitoring_financed_description_named_management,
                         organizationName, organizationName);
@@ -509,9 +508,6 @@
                 return mContext.getString(
                         R.string.monitoring_description_named_management, organizationName);
             }
-        } else if (isProfileOwnerOfOrganizationOwnedDevice && workProfileOrganizationName != null) {
-            return mContext.getString(
-                    R.string.monitoring_description_named_management, workProfileOrganizationName);
         }
         return mContext.getString(R.string.monitoring_description_management);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 1ff30a3..6baacb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -57,6 +57,7 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.statusbar.IStatusBar;
 import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.util.GcUtils;
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.commandline.CommandRegistry;
@@ -1086,6 +1087,12 @@
         thr.start();
     }
 
+    @Override
+    public void runGcForTest() {
+        // Gc sysui
+        GcUtils.runGcAndFinalizersSync();
+    }
+
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 875696a..2530cfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -35,6 +35,7 @@
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.Vibrator;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
@@ -114,6 +115,8 @@
     private AccessibilityManager mAccessibilityManager;
     @Mock
     private ScreenLifecycle mScreenLifecycle;
+    @Mock
+    private Vibrator mVibrator;
 
     private FakeExecutor mFgExecutor;
 
@@ -170,7 +173,8 @@
                 mFalsingManager,
                 mPowerManager,
                 mAccessibilityManager,
-                mScreenLifecycle);
+                mScreenLifecycle,
+                mVibrator);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -302,4 +306,29 @@
         // THEN no illumination because screen is off
         verify(mUdfpsView, never()).startIllumination(any());
     }
+
+    @Test
+    public void playHapticOnTouchUdfpsArea() throws RemoteException {
+        // Configure UdfpsView to accept the ACTION_DOWN event
+        when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN that the overlay is showing
+        mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        // WHEN ACTION_DOWN is received
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        downEvent.recycle();
+        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+        moveEvent.recycle();
+
+        // THEN click haptic is played
+        verify(mVibrator).vibrate(mUdfpsController.mEffectClick,
+                UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 234dec2..7caf0dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -21,6 +21,7 @@
 import static junit.framework.Assert.assertNotNull;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -189,6 +190,29 @@
     }
 
     @Test
+    public void testUntappableView_profileOwnerOfOrgOwnedDevice() {
+        when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
+
+        mFooter.refreshState();
+
+        TestableLooper.get(this).processAllMessages();
+        assertFalse(mRootView.isClickable());
+        assertEquals(View.GONE, mRootView.findViewById(R.id.footer_icon).getVisibility());
+    }
+
+    @Test
+    public void testTappableView_profileOwnerOfOrgOwnedDevice_networkLoggingEnabled() {
+        when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
+        when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
+
+        mFooter.refreshState();
+
+        TestableLooper.get(this).processAllMessages();
+        assertTrue(mRootView.isClickable());
+        assertEquals(View.VISIBLE, mRootView.findViewById(R.id.footer_icon).getVisibility());
+    }
+
+    @Test
     public void testNetworkLoggingEnabled_deviceOwner() {
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
         when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
@@ -435,10 +459,7 @@
     @Test
     public void testGetManagementMessage_noManagement() {
         assertEquals(null, mFooter.getManagementMessage(
-                /* isDeviceManaged= */ false,
-                MANAGING_ORGANIZATION,
-                /* isProfileOwnerOfOrganizationOwnedDevice= */ false,
-                MANAGING_ORGANIZATION));
+                /* isDeviceManaged= */ false, MANAGING_ORGANIZATION));
     }
 
     @Test
@@ -446,16 +467,11 @@
         assertEquals(mContext.getString(R.string.monitoring_description_named_management,
                                         MANAGING_ORGANIZATION),
                      mFooter.getManagementMessage(
-                             /* isDeviceManaged= */ true,
-                             MANAGING_ORGANIZATION,
-                             /* isProfileOwnerOfOrganizationOwnedDevice= */ false,
-                             /* workProfileOrganizationName= */ null));
+                             /* isDeviceManaged= */ true, MANAGING_ORGANIZATION));
         assertEquals(mContext.getString(R.string.monitoring_description_management),
                      mFooter.getManagementMessage(
                              /* isDeviceManaged= */ true,
-                             /* organizationName= */ null,
-                             /* isProfileOwnerOfOrganizationOwnedDevice= */ false,
-                             /* workProfileOrganizationName= */ null));
+                             /* organizationName= */ null));
     }
 
     @Test
@@ -467,27 +483,7 @@
         assertEquals(mContext.getString(R.string.monitoring_financed_description_named_management,
                 MANAGING_ORGANIZATION, MANAGING_ORGANIZATION),
                 mFooter.getManagementMessage(
-                        /* isDeviceManaged= */ true,
-                        MANAGING_ORGANIZATION,
-                        /* isProfileOwnerOfOrganizationOwnedDevice= */ false,
-                        /* workProfileOrganizationName= */ null));
-    }
-
-    @Test
-    public void testGetManagementMessage_profileOwnerOfOrganizationOwnedDevice() {
-        assertEquals(mContext.getString(R.string.monitoring_description_named_management,
-                MANAGING_ORGANIZATION),
-                mFooter.getManagementMessage(
-                        /* isDeviceManaged= */ false,
-                        /* organizationName= */ null,
-                        /* isProfileOwnerOfOrganizationOwnedDevice= */ true,
-                        MANAGING_ORGANIZATION));
-        assertEquals(mContext.getString(R.string.monitoring_description_management),
-                mFooter.getManagementMessage(
-                        /* isDeviceManaged= */ false,
-                        /* organizationName= */ null,
-                        /* isProfileOwnerOfOrganizationOwnedDevice= */ true,
-                        /* workProfileOrganizationName= */ null));
+                        /* isDeviceManaged= */ true, MANAGING_ORGANIZATION));
     }
 
     @Test
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 794cb93..d574e74 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -49,6 +49,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.system.ErrnoException;
@@ -65,6 +66,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 import com.android.net.module.util.NetdUtils;
+import com.android.net.module.util.PermissionUtils;
 
 import libcore.io.IoUtils;
 
@@ -466,8 +468,7 @@
 
         /** Safety method; guards against access of other user's UserRecords */
         private void checkCallerUid(int uid) {
-            if (uid != Binder.getCallingUid()
-                    && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
+            if (uid != Binder.getCallingUid() && Process.SYSTEM_UID != Binder.getCallingUid()) {
                 throw new SecurityException("Attempted access of unowned resources");
             }
         }
@@ -1105,11 +1106,15 @@
      * Checks the user-provided direction field and throws an IllegalArgumentException if it is not
      * DIRECTION_IN or DIRECTION_OUT
      */
-    private static void checkDirection(int direction) {
+    private void checkDirection(int direction) {
         switch (direction) {
             case IpSecManager.DIRECTION_OUT:
             case IpSecManager.DIRECTION_IN:
                 return;
+            case IpSecManager.DIRECTION_FWD:
+                // Only NETWORK_STACK or PERMISSION_NETWORK_STACK allowed to use forward policies
+                PermissionUtils.enforceNetworkStackPermission(mContext);
+                return;
         }
         throw new IllegalArgumentException("Invalid Direction: " + direction);
     }
@@ -1353,6 +1358,16 @@
                         ikey,
                         0xffffffff,
                         resourceId);
+                netd.ipSecAddSecurityPolicy(
+                        callerUid,
+                        selAddrFamily,
+                        IpSecManager.DIRECTION_FWD,
+                        remoteAddr,
+                        localAddr,
+                        0,
+                        ikey,
+                        0xffffffff,
+                        resourceId);
             }
 
             userRecord.mTunnelInterfaceRecords.put(
@@ -1820,7 +1835,7 @@
         int mark =
                 (direction == IpSecManager.DIRECTION_OUT)
                         ? tunnelInterfaceInfo.getOkey()
-                        : tunnelInterfaceInfo.getIkey();
+                        : tunnelInterfaceInfo.getIkey(); // Ikey also used for FWD policies
 
         try {
             // Default to using the invalid SPI of 0 for inbound SAs. This allows policies to skip
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 1bd53ae..aadb25c 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -24,6 +24,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
 import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
+import static android.os.PowerExemptionManager.REASON_OPT_OUT_REQUESTED;
 import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_PLATFORM_VPN;
 import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_VPN;
 import static android.os.PowerExemptionManager.REASON_TEMP_ALLOWED_WHILE_IN_USE;
@@ -5874,7 +5875,12 @@
                 ret = REASON_OP_ACTIVATE_PLATFORM_VPN;
             }
         }
-
+        if (ret == REASON_DENIED) {
+            if (mAm.mConstants.mFgsAllowOptOut
+                    && targetService.appInfo.hasRequestForegroundServiceExemption()) {
+                ret = REASON_OPT_OUT_REQUESTED;
+            }
+        }
         return ret;
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 0979585..0fff8be 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -110,6 +110,7 @@
     static final String KEY_FGS_START_FOREGROUND_TIMEOUT = "fgs_start_foreground_timeout";
     static final String KEY_FGS_ATOM_SAMPLE_RATE = "fgs_atom_sample_rate";
     static final String KEY_KILL_FAS_CACHED_IDLE = "kill_fas_cached_idle";
+    static final String KEY_FGS_ALLOW_OPT_OUT = "fgs_allow_opt_out";
 
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
     private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
@@ -161,6 +162,7 @@
      */
     private static final int
             DEFAULT_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR = 1;
+    private static final boolean DEFAULT_FGS_ALLOW_OPT_OUT = false;
 
     // Flag stored in the DeviceConfig API.
     /**
@@ -493,6 +495,12 @@
      */
     volatile boolean mKillForceAppStandByAndCachedIdle = DEFAULT_KILL_FAS_CACHED_IDLE;
 
+    /**
+     * Whether to allow "opt-out" from the foreground service restrictions.
+     * (https://developer.android.com/about/versions/12/foreground-services)
+     */
+    volatile boolean mFgsAllowOptOut = DEFAULT_FGS_ALLOW_OPT_OUT;
+
     private final ActivityManagerService mService;
     private ContentResolver mResolver;
     private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -701,6 +709,9 @@
                             case KEY_KILL_FAS_CACHED_IDLE:
                                 updateKillFasCachedIdle();
                                 break;
+                            case KEY_FGS_ALLOW_OPT_OUT:
+                                updateFgsAllowOptOut();
+                                break;
                             default:
                                 break;
                         }
@@ -1040,6 +1051,13 @@
                 DEFAULT_KILL_FAS_CACHED_IDLE);
     }
 
+    private void updateFgsAllowOptOut() {
+        mFgsAllowOptOut = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_FGS_ALLOW_OPT_OUT,
+                DEFAULT_FGS_ALLOW_OPT_OUT);
+    }
+
     private void updateImperceptibleKillExemptions() {
         IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.clear();
         IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages);
@@ -1260,6 +1278,8 @@
         pw.print("="); pw.println(mFgsAtomSampleRate);
         pw.print("  "); pw.print(KEY_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR);
         pw.print("="); pw.println(mPushMessagingOverQuotaBehavior);
+        pw.print("  "); pw.print(KEY_FGS_ALLOW_OPT_OUT);
+        pw.print("="); pw.println(mFgsAllowOptOut);
 
         pw.println();
         if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 99b0d81..50515882 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -30,6 +30,7 @@
 import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.NoteOpEvent;
@@ -827,6 +828,14 @@
         @GuardedBy("AppOpsService.this")
         private @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents;
 
+        /**
+         * Currently paused startOp events
+         *
+         * <p>Key is clientId
+         */
+        @GuardedBy("AppOpsService.this")
+        private @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
+
         AttributedOp(@Nullable String tag, @NonNull Op parent) {
             this.tag = tag;
             this.parent = parent;
@@ -944,23 +953,36 @@
                 @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
                 @AppOpsManager.UidState int uidState, @OpFlags int flags,
                 boolean triggerCallbackIfNeeded) throws RemoteException {
-            if (triggerCallbackIfNeeded && !parent.isRunning()) {
+            startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
+                    uidState, flags, triggerCallbackIfNeeded, true);
+        }
+
+        private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
+                @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+                @AppOpsManager.UidState int uidState, @OpFlags int flags,
+                boolean triggerCallbackIfNeeded, boolean isStarted) throws RemoteException {
+            if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) {
                 scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
                         parent.packageName, true);
             }
 
-            if (mInProgressEvents == null) {
+
+            if (isStarted && mInProgressEvents == null) {
                 mInProgressEvents = new ArrayMap<>(1);
+            } else if (mPausedInProgressEvents == null) {
+                mPausedInProgressEvents = new ArrayMap<>(1);
             }
+            ArrayMap<IBinder, InProgressStartOpEvent> events = isStarted
+                    ? mInProgressEvents : mPausedInProgressEvents;
 
             long startTime = System.currentTimeMillis();
-            InProgressStartOpEvent event = mInProgressEvents.get(clientId);
+            InProgressStartOpEvent event = events.get(clientId);
             if (event == null) {
                 event = mInProgressStartOpEventPool.acquire(startTime,
                         SystemClock.elapsedRealtime(), clientId,
                         PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
                         proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags);
-                mInProgressEvents.put(clientId, event);
+                events.put(clientId, event);
             } else {
                 if (uidState != event.mUidState) {
                     onUidStateChanged(uidState);
@@ -969,12 +991,15 @@
 
             event.numUnfinishedStarts++;
 
-            mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
-                    tag, uidState, flags, startTime);
+            if (isStarted) {
+                mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
+                        parent.packageName, tag, uidState, flags, startTime);
+            }
+
         }
 
         /**
-         * Update state when finishOp was called
+         * Update state when finishOp was called. Will finish started ops, and delete paused ops.
          *
          * @param clientId Id of the finishOp caller
          */
@@ -983,22 +1008,32 @@
         }
 
         private void finished(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded) {
-            if (mInProgressEvents == null) {
-                Slog.wtf(TAG, "No ops running");
-                return;
-            }
+            finishOrPause(clientId, triggerCallbackIfNeeded, false);
+        }
 
-            int indexOfToken = mInProgressEvents.indexOfKey(clientId);
+        /**
+         * Update state when paused or finished is called. If pausing, it records the op as
+         * stopping in the HistoricalRegistry, but does not delete it.
+         */
+        private void finishOrPause(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded,
+                boolean isPausing) {
+            int indexOfToken = mInProgressEvents != null
+                    ? mInProgressEvents.indexOfKey(clientId) : -1;
             if (indexOfToken < 0) {
-                Slog.wtf(TAG, "No op running for the client");
+                finishPossiblyPaused(clientId, isPausing);
                 return;
             }
 
             InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken);
-            event.numUnfinishedStarts--;
-            if (event.numUnfinishedStarts == 0) {
-                event.finish();
-                mInProgressEvents.removeAt(indexOfToken);
+            if (!isPausing) {
+                event.numUnfinishedStarts--;
+            }
+            // If we are pausing, create a NoteOpEvent, but don't change the InProgress event
+            if (event.numUnfinishedStarts == 0 || isPausing) {
+                if (!isPausing) {
+                    event.finish();
+                    mInProgressEvents.removeAt(indexOfToken);
+                }
 
                 if (mAccessEvents == null) {
                     mAccessEvents = new LongSparseArray<>(1);
@@ -1018,20 +1053,112 @@
                         parent.packageName, tag, event.getUidState(),
                         event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration());
 
-                mInProgressStartOpEventPool.release(event);
+                if (!isPausing) {
+                    mInProgressStartOpEventPool.release(event);
+                    if (mInProgressEvents.isEmpty()) {
+                        mInProgressEvents = null;
 
-                if (mInProgressEvents.isEmpty()) {
-                    mInProgressEvents = null;
-
-                    // TODO moltmann: Also callback for single attribution tag activity changes
-                    if (triggerCallbackIfNeeded && !parent.isRunning()) {
-                        scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
-                                parent.packageName, false);
+                        // TODO ntmyren: Also callback for single attribution tag activity changes
+                        if (triggerCallbackIfNeeded && !parent.isRunning()) {
+                            scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
+                                    parent.packageName, false);
+                        }
                     }
                 }
             }
         }
 
+        // Finish or pause (no-op) an already paused op
+        private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
+            if (mPausedInProgressEvents == null) {
+                Slog.wtf(TAG, "No ops running or paused");
+                return;
+            }
+
+            int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
+            if (indexOfToken < 0) {
+                Slog.wtf(TAG, "No op running or paused for the client");
+                return;
+            } else if (isPausing) {
+                // already paused
+                return;
+            }
+
+            // no need to record a paused event finishing.
+            InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken);
+            event.numUnfinishedStarts--;
+            if (event.numUnfinishedStarts == 0) {
+                mPausedInProgressEvents.removeAt(indexOfToken);
+                mInProgressStartOpEventPool.release(event);
+                if (mPausedInProgressEvents.isEmpty()) {
+                    mPausedInProgressEvents = null;
+                }
+            }
+        }
+
+        /**
+         * Create an event that will be started, if the op is unpaused.
+         */
+        public void createPaused(@NonNull IBinder clientId, int proxyUid,
+                @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+                @AppOpsManager.UidState int uidState, @OpFlags int flags) throws RemoteException {
+            startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
+                    uidState, flags, true, false);
+        }
+
+        /**
+         * Pause all currently started ops. This will create a HistoricalRegistry
+         */
+        public void pause() {
+            if (mInProgressEvents == null) {
+                return;
+            }
+
+            if (mPausedInProgressEvents == null) {
+                mPausedInProgressEvents = new ArrayMap<>(1);
+            }
+
+            for (int i = 0; i < mInProgressEvents.size(); i++) {
+                InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
+                mPausedInProgressEvents.put(event.mClientId, event);
+                finishOrPause(event.mClientId, true, true);
+            }
+            scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
+                    parent.packageName, false);
+            mInProgressEvents = null;
+        }
+
+        /**
+         * Unpause all currently paused ops. This will reinitialize their start and duration
+         * times, but keep all other values the same
+         */
+        public void resume() {
+            if (mPausedInProgressEvents == null) {
+                return;
+            }
+
+            if (mInProgressEvents == null) {
+                mInProgressEvents = new ArrayMap<>(mPausedInProgressEvents.size());
+            }
+            boolean shouldSendActive = !mPausedInProgressEvents.isEmpty()
+                    && mInProgressEvents.isEmpty();
+
+            long startTime = System.currentTimeMillis();
+            for (int i = 0; i < mPausedInProgressEvents.size(); i++) {
+                InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i);
+                mInProgressEvents.put(event.mClientId, event);
+                event.mStartElapsedTime = SystemClock.elapsedRealtime();
+                event.mStartTime = startTime;
+                mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
+                        parent.packageName, tag, event.mUidState, event.mFlags, startTime);
+            }
+            if (shouldSendActive) {
+                scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
+                        parent.packageName, true);
+            }
+            mPausedInProgressEvents = null;
+        }
+
         /**
          * Called in the case the client dies without calling finish first
          *
@@ -2303,6 +2430,9 @@
                 scheduleWriteLocked();
             }
             uidState.evalForegroundOps(mOpModeWatchers);
+            if (mode != MODE_ERRORED && mode != previousMode) {
+                updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+            }
         }
 
         notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
@@ -2556,6 +2686,9 @@
                         pruneOpLocked(op, uid, packageName);
                     }
                     scheduleFastWriteLocked();
+                    if (mode != MODE_ERRORED) {
+                        updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+                    }
                 }
             }
         }
@@ -3660,6 +3793,7 @@
                     packageName);
         }
 
+        boolean isRestricted = false;
         synchronized (this) {
             final Ops ops = getOpsLocked(uid, packageName, attributionTag, bypass, true /* edit */);
             if (ops == null) {
@@ -3673,18 +3807,10 @@
                         packageName);
             }
             final Op op = getOpLocked(ops, code, uid, true);
-            if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
-                if (!dryRun) {
-                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                            flags, AppOpsManager.MODE_IGNORED);
-                }
-                return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
-                        packageName);
-            }
-
             final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
-            final int switchCode = AppOpsManager.opToSwitch(code);
             final UidState uidState = ops.uidState;
+            isRestricted = isOpRestrictedLocked(uid, code, packageName, bypass);
+            final int switchCode = AppOpsManager.opToSwitch(code);
             // If there is a non-default per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
             if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
@@ -3720,25 +3846,30 @@
                 }
             }
             if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
-                    + " package " + packageName);
+                    + " package " + packageName + " restricted: " + isRestricted);
             if (!dryRun) {
-                scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        AppOpsManager.MODE_ALLOWED);
                 try {
-                    attributedOp.started(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
-                            uidState.state, flags);
+                    if (isRestricted) {
+                        attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
+                                proxyAttributionTag, uidState.state, flags);
+                    } else {
+                        attributedOp.started(clientId, proxyUid, proxyPackageName,
+                                proxyAttributionTag, uidState.state, flags);
+                    }
                 } catch (RemoteException e) {
                     throw new RuntimeException(e);
                 }
+                scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                        isRestricted ? MODE_IGNORED : MODE_ALLOWED);
             }
         }
 
-        if (shouldCollectAsyncNotedOp && !dryRun) {
+        if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) {
             collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
                     message, shouldCollectMessage);
         }
 
-        return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
+        return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
                 packageName);
     }
 
@@ -6116,6 +6247,9 @@
             if (restrictionState.setRestriction(code, restricted, exceptionPackages, userHandle)) {
                 mHandler.sendMessage(PooledLambda.obtainMessage(
                         AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsService::updateStartedOpModeForUser, this, code, restricted,
+                        userHandle));
             }
 
             if (restrictionState.isDefault()) {
@@ -6125,6 +6259,44 @@
         }
     }
 
+    private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
+        synchronized (AppOpsService.this) {
+            int numUids = mUidStates.size();
+            for (int uidNum = 0; uidNum < numUids; uidNum++) {
+                int uid = mUidStates.keyAt(uidNum);
+                if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
+                    continue;
+                }
+                updateStartedOpModeForUidLocked(code, restricted, uid);
+            }
+        }
+    }
+
+    private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
+        UidState uidState = mUidStates.get(uid);
+        if (uidState == null || uidState.pkgOps == null) {
+            return;
+        }
+
+        int numPkgOps = uidState.pkgOps.size();
+        for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
+            Ops ops = uidState.pkgOps.valueAt(pkgNum);
+            Op op = ops != null ? ops.get(code) : null;
+            if (op == null || (op.mode != MODE_ALLOWED && op.mode != MODE_FOREGROUND)) {
+                continue;
+            }
+            int numAttrTags = op.mAttributions.size();
+            for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
+                AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
+                if (restricted) {
+                    attrOp.pause();
+                } else {
+                    attrOp.resume();
+                }
+            }
+        }
+    }
+
     private void notifyWatchersOfChange(int code, int uid) {
         final ArraySet<ModeCallback> clonedCallbacks;
         synchronized (this) {
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 5556015..35e8d34 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -555,8 +555,6 @@
                     }
                     if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE) {
                         mDiscreteRegistry.setDebugMode(true);
-                    } else {
-                        mDiscreteRegistry.setDebugMode(false);
                     }
                 }
                 if (mBaseSnapshotInterval != baseSnapshotInterval) {
@@ -627,6 +625,7 @@
         }
         setHistoryParameters(DEFAULT_MODE, DEFAULT_SNAPSHOT_INTERVAL_MILLIS,
                 DEFAULT_COMPRESSION_STEP);
+        mDiscreteRegistry.setDebugMode(false);
     }
 
     void clearHistory(int uid, String packageName) {
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 1261296..2c2a2bf 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -567,7 +567,11 @@
                 out.attributeLong(null, ATTR_TIMESTAMP, toWrite[i].timeStamp);
                 out.attribute(null, ATTR_PACKAGE_NAME, toWrite[i].packageName);
                 out.attributeInt(null, ATTR_USER, userSerialNo);
-                out.attribute(null, ATTR_UNIQUE_DISPLAY_ID, toWrite[i].uniqueDisplayId);
+                String uniqueDisplayId = toWrite[i].uniqueDisplayId;
+                if (uniqueDisplayId == null) {
+                    uniqueDisplayId = "";
+                }
+                out.attribute(null, ATTR_UNIQUE_DISPLAY_ID, uniqueDisplayId);
                 out.attributeFloat(null, ATTR_BATTERY_LEVEL, toWrite[i].batteryLevel);
                 out.attributeBoolean(null, ATTR_NIGHT_MODE, toWrite[i].nightMode);
                 out.attributeInt(null, ATTR_COLOR_TEMPERATURE,
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index f212698..40ab108 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -19,6 +19,8 @@
 import static com.android.server.wm.utils.RotationAnimationUtils.hasProtectedContent;
 
 import android.content.Context;
+import android.graphics.BLASTBufferQueue;
+import android.graphics.PixelFormat;
 import android.graphics.SurfaceTexture;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
@@ -91,6 +93,7 @@
     private int mDisplayHeight;     // real height, not rotated
     private SurfaceControl mSurfaceControl;
     private Surface mSurface;
+    private BLASTBufferQueue mBLASTBufferQueue;
     private NaturalSurfaceLayout mSurfaceLayout;
     private EGLDisplay mEglDisplay;
     private EGLConfig mEglConfig;
@@ -165,21 +168,30 @@
                     "Failed to take screenshot because internal display is disconnected");
             return false;
         }
-        boolean isWideColor = SurfaceControl.getDynamicDisplayInfo(token).activeColorMode
+        final boolean isWideColor = SurfaceControl.getDynamicDisplayInfo(token).activeColorMode
                 == Display.COLOR_MODE_DISPLAY_P3;
 
         // Set mPrepared here so if initialization fails, resources can be cleaned up.
         mPrepared = true;
 
-        SurfaceControl.ScreenshotHardwareBuffer hardwareBuffer = captureScreen();
+        final SurfaceControl.ScreenshotHardwareBuffer hardwareBuffer = captureScreen();
         if (hardwareBuffer == null) {
             dismiss();
             return false;
         }
 
-        boolean isProtected = hasProtectedContent(hardwareBuffer.getHardwareBuffer());
-        if (!(createSurfaceControl(hardwareBuffer.containsSecureLayers())
-                && createEglContext(isProtected) && createEglSurface(isProtected, isWideColor)
+        final boolean isProtected = hasProtectedContent(hardwareBuffer.getHardwareBuffer());
+        if (!createSurfaceControl(hardwareBuffer.containsSecureLayers())) {
+            dismiss();
+            return false;
+        }
+
+        // MODE_FADE use ColorLayer to implement.
+        if (mMode == MODE_FADE) {
+            return true;
+        }
+
+        if (!(createEglContext(isProtected) && createEglSurface(isProtected, isWideColor)
                 && setScreenshotTextureAndSetViewport(hardwareBuffer))) {
             dismiss();
             return false;
@@ -190,7 +202,7 @@
             return false;
         }
         try {
-            if(!initGLShaders(context) || !initGLBuffers() || checkGlErrors("prepare")) {
+            if (!initGLShaders(context) || !initGLBuffers() || checkGlErrors("prepare")) {
                 detachEglContext();
                 dismiss();
                 return false;
@@ -564,7 +576,7 @@
             if (mMode == MODE_FADE) {
                 builder.setColorLayer();
             } else {
-                builder.setBufferSize(mDisplayWidth, mDisplayHeight);
+                builder.setBLASTLayer();
             }
             mSurfaceControl = builder.build();
         } catch (OutOfResourcesException ex) {
@@ -579,9 +591,11 @@
         mSurfaceLayout.onDisplayTransaction(mTransaction);
         mTransaction.apply();
 
-        mSurface = new Surface();
-        mSurface.copyFrom(mSurfaceControl);
-
+        if (mMode != MODE_FADE) {
+            mBLASTBufferQueue = new BLASTBufferQueue("ColorFade", mSurfaceControl,
+                    mDisplayWidth, mDisplayHeight, PixelFormat.TRANSLUCENT);
+            mSurface = mBLASTBufferQueue.createSurface();
+        }
         return true;
     }
 
@@ -707,7 +721,12 @@
             mSurfaceLayout.dispose();
             mSurfaceLayout = null;
             mTransaction.remove(mSurfaceControl).apply();
-            mSurface.release();
+            if (mSurface != null) {
+                mSurface.release();
+                mBLASTBufferQueue.destroy();
+                mSurface = null;
+                mBLASTBufferQueue = null;
+            }
             mSurfaceControl = null;
             mSurfaceVisible = false;
             mSurfaceAlpha = 0f;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index cb05f8f..fefe953 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -221,7 +221,7 @@
         }
     }
 
-    private class Setting {
+    protected class Setting {
         @NonNull private final Context mContext;
         @NonNull private final @CecSettingName String mName;
         private final boolean mUserConfigurable;
@@ -566,7 +566,7 @@
         }
     }
 
-    private String retrieveValue(@NonNull Setting setting, @NonNull String defaultValue) {
+    protected String retrieveValue(@NonNull Setting setting, @NonNull String defaultValue) {
         @Storage int storage = getStorage(setting);
         String storageKey = getStorageKey(setting);
         if (storage == STORAGE_SYSPROPS) {
@@ -582,7 +582,7 @@
         return null;
     }
 
-    private void storeValue(@NonNull Setting setting, @NonNull String value) {
+    protected void storeValue(@NonNull Setting setting, @NonNull String value) {
         @Storage int storage = getStorage(setting);
         String storageKey = getStorageKey(setting);
         if (storage == STORAGE_SYSPROPS) {
@@ -626,7 +626,7 @@
         notifySettingChanged(setting);
     }
 
-    private void notifySettingChanged(@NonNull Setting setting) {
+    protected void notifySettingChanged(@NonNull Setting setting) {
         synchronized (mLock) {
             ArrayMap<SettingChangeListener, Executor> listeners =
                     mSettingChangeListeners.get(setting);
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 172a68a..c55cf51 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -269,6 +269,13 @@
 
         mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
                 this::onLocationModeChanged);
+        mInjector.getSettingsHelper().addOnIgnoreSettingsPackageWhitelistChangedListener(
+                () -> refreshAppOpsRestrictions(UserHandle.USER_ALL));
+        mInjector.getUserInfoHelper().addListener((userId, change) -> {
+            if (change == UserInfoHelper.UserListener.USER_STARTED) {
+                refreshAppOpsRestrictions(userId);
+            }
+        });
 
         // set up passive provider first since it will be required for all other location providers,
         // which are loaded later once the system is ready.
@@ -482,6 +489,8 @@
                 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+
+        refreshAppOpsRestrictions(userId);
     }
 
     @Override
@@ -1347,6 +1356,43 @@
         ipw.decreaseIndent();
     }
 
+    private void refreshAppOpsRestrictions(int userId) {
+        if (userId == UserHandle.USER_ALL) {
+            final int[] runningUserIds = mInjector.getUserInfoHelper().getRunningUserIds();
+            for (int i = 0; i < runningUserIds.length; i++) {
+                refreshAppOpsRestrictions(runningUserIds[i]);
+            }
+            return;
+        }
+
+        Preconditions.checkArgument(userId >= 0);
+
+        ArraySet<String> packages = new ArraySet<>();
+        for (LocationProviderManager manager : mProviderManagers) {
+            packages.add(manager.getIdentity().getPackageName());
+        }
+        packages.add(mContext.getPackageName());
+        packages.addAll(mInjector.getSettingsHelper().getIgnoreSettingsPackageWhitelist());
+        String[] allowedPackages = packages.toArray(new String[0]);
+
+        boolean enabled = mInjector.getSettingsHelper().isLocationEnabled(userId);
+
+        AppOpsManager appOpsManager = Objects.requireNonNull(
+                mContext.getSystemService(AppOpsManager.class));
+        appOpsManager.setUserRestrictionForUser(
+                AppOpsManager.OP_COARSE_LOCATION,
+                enabled,
+                LocationManagerService.this,
+                allowedPackages,
+                userId);
+        appOpsManager.setUserRestrictionForUser(
+                AppOpsManager.OP_FINE_LOCATION,
+                enabled,
+                LocationManagerService.this,
+                allowedPackages,
+                userId);
+    }
+
     private class LocalService extends LocationManagerInternal {
 
         LocalService() {}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index af0aa76..77c1c1d 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -16,7 +16,6 @@
 
 package com.android.server.pm;
 
-import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import android.annotation.Nullable;
@@ -24,8 +23,11 @@
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageInfo;
 import android.os.BatteryManagerInternal;
 import android.os.Environment;
@@ -35,6 +37,7 @@
 import android.os.storage.StorageManager;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Slog;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
@@ -63,9 +66,7 @@
     private static final int JOB_IDLE_OPTIMIZE = 800;
     private static final int JOB_POST_BOOT_UPDATE = 801;
 
-    private static final long IDLE_OPTIMIZATION_PERIOD = DEBUG
-            ? TimeUnit.MINUTES.toMillis(1)
-            : TimeUnit.DAYS.toMillis(1);
+    private static final long IDLE_OPTIMIZATION_PERIOD = TimeUnit.DAYS.toMillis(1);
 
     private static ComponentName sDexoptServiceName = new ComponentName(
             "android",
@@ -113,14 +114,24 @@
             return;
         }
 
-        JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+        final JobScheduler js = context.getSystemService(JobScheduler.class);
 
         // Schedule a one-off job which scans installed packages and updates
-        // out-of-date oat files.
-        js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName)
-                    .setMinimumLatency(TimeUnit.MINUTES.toMillis(10))
-                    .setOverrideDeadline(TimeUnit.MINUTES.toMillis(60))
-                    .build());
+        // out-of-date oat files. Schedule it 10 minutes after the boot complete event,
+        // so that we don't overload the boot with additional dex2oat compilations.
+        context.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName)
+                        .setMinimumLatency(TimeUnit.MINUTES.toMillis(10))
+                        .setOverrideDeadline(TimeUnit.MINUTES.toMillis(60))
+                        .build());
+                context.unregisterReceiver(this);
+                if (DEBUG) {
+                    Slog.i(TAG, "BootBgDexopt scheduled");
+                }
+            }
+        }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
 
         // Schedule a daily job which scans installed packages and compiles
         // those with fresh profiling data.
@@ -130,8 +141,8 @@
                     .setPeriodic(IDLE_OPTIMIZATION_PERIOD)
                     .build());
 
-        if (DEBUG_DEXOPT) {
-            Log.i(TAG, "Jobs scheduled");
+        if (DEBUG) {
+            Slog.d(TAG, "BgDexopt scheduled");
         }
     }
 
@@ -151,7 +162,7 @@
         @SuppressWarnings("deprecation")
         final long lowThreshold = StorageManager.from(context).getStorageLowBytes(mDataDir);
         if (lowThreshold == 0) {
-            Log.e(TAG, "Invalid low storage threshold");
+            Slog.e(TAG, "Invalid low storage threshold");
         }
 
         return lowThreshold;
@@ -198,13 +209,12 @@
             long usableSpace = mDataDir.getUsableSpace();
             if (usableSpace < lowThreshold) {
                 // Rather bail than completely fill up the disk.
-                Log.w(TAG, "Aborting background dex opt job due to low storage: " +
+                Slog.w(TAG, "Aborting background dex opt job due to low storage: " +
                         usableSpace);
                 break;
             }
-
-            if (DEBUG_DEXOPT) {
-                Log.i(TAG, "Updating package " + pkg);
+            if (DEBUG) {
+                Slog.i(TAG, "Updating package " + pkg);
             }
 
             // Update package if needed. Note that there can be no race between concurrent
@@ -236,13 +246,13 @@
             public void run() {
                 int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this);
                 if (result == OPTIMIZE_PROCESSED) {
-                    Log.i(TAG, "Idle optimizations completed.");
+                    Slog.i(TAG, "Idle optimizations completed.");
                 } else if (result == OPTIMIZE_ABORT_NO_SPACE_LEFT) {
-                    Log.w(TAG, "Idle optimizations aborted because of space constraints.");
+                    Slog.w(TAG, "Idle optimizations aborted because of space constraints.");
                 } else if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
-                    Log.w(TAG, "Idle optimizations aborted by job scheduler.");
+                    Slog.w(TAG, "Idle optimizations aborted by job scheduler.");
                 } else {
-                    Log.w(TAG, "Idle optimizations ended with unexpected code: " + result);
+                    Slog.w(TAG, "Idle optimizations ended with unexpected code: " + result);
                 }
                 if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
                     // Abandon our timeslice and do not reschedule.
@@ -256,7 +266,7 @@
     // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes).
     private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs,
             Context context) {
-        Log.i(TAG, "Performing idle optimizations");
+        Slog.i(TAG, "Performing idle optimizations");
         // If post-boot update is still running, request that it exits early.
         mExitPostBootUpdate.set(true);
         mAbortIdleOptimization.set(false);
@@ -331,11 +341,15 @@
             final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE
                     * lowStorageThreshold;
             boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
-            Log.d(TAG, "Should Downgrade " + shouldDowngrade);
+            if (DEBUG) {
+                Slog.d(TAG, "Should Downgrade " + shouldDowngrade);
+            }
             if (shouldDowngrade) {
                 Set<String> unusedPackages =
                         pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
-                Log.d(TAG, "Unsused Packages " +  String.join(",", unusedPackages));
+                if (DEBUG) {
+                    Slog.d(TAG, "Unsused Packages " +  String.join(",", unusedPackages));
+                }
 
                 if (!unusedPackages.isEmpty()) {
                     for (String pkg : unusedPackages) {
@@ -407,7 +421,9 @@
      */
     private boolean downgradePackage(PackageManagerService pm, String pkg,
             boolean isForPrimaryDex) {
-        Log.d(TAG, "Downgrading " + pkg);
+        if (DEBUG) {
+            Slog.d(TAG, "Downgrading " + pkg);
+        }
         boolean dex_opt_performed = false;
         int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
         int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
@@ -529,7 +545,7 @@
         long usableSpace = mDataDir.getUsableSpace();
         if (usableSpace < lowStorageThreshold) {
             // Rather bail than completely fill up the disk.
-            Log.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace);
+            Slog.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace);
             return OPTIMIZE_ABORT_NO_SPACE_LEFT;
         }
 
@@ -568,8 +584,8 @@
 
     @Override
     public boolean onStartJob(JobParameters params) {
-        if (DEBUG_DEXOPT) {
-            Log.i(TAG, "onStartJob");
+        if (DEBUG) {
+            Slog.i(TAG, "onStartJob");
         }
 
         // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
@@ -577,17 +593,13 @@
         // restart with a period of ~1 minute.
         PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
         if (pm.isStorageLow()) {
-            if (DEBUG_DEXOPT) {
-                Log.i(TAG, "Low storage, skipping this run");
-            }
+            Slog.i(TAG, "Low storage, skipping this run");
             return false;
         }
 
         final ArraySet<String> pkgs = pm.getOptimizablePackages();
         if (pkgs.isEmpty()) {
-            if (DEBUG_DEXOPT) {
-                Log.i(TAG, "No packages to optimize");
-            }
+            Slog.i(TAG, "No packages to optimize");
             return false;
         }
 
@@ -603,8 +615,8 @@
 
     @Override
     public boolean onStopJob(JobParameters params) {
-        if (DEBUG_DEXOPT) {
-            Log.i(TAG, "onStopJob");
+        if (DEBUG) {
+            Slog.d(TAG, "onStopJob");
         }
 
         if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
@@ -625,7 +637,7 @@
     private void notifyPinService(ArraySet<String> updatedPackages) {
         PinnerService pinnerService = LocalServices.getService(PinnerService.class);
         if (pinnerService != null) {
-            Log.i(TAG, "Pinning optimized code " + updatedPackages);
+            Slog.i(TAG, "Pinning optimized code " + updatedPackages);
             pinnerService.update(updatedPackages, false /* force */);
         }
     }
@@ -660,7 +672,7 @@
         final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
         String sysPropValue = SystemProperties.get(sysPropKey);
         if (sysPropValue == null || sysPropValue.isEmpty()) {
-            Log.w(TAG, "SysProp " + sysPropKey + " not set");
+            Slog.w(TAG, "SysProp " + sysPropKey + " not set");
             return Long.MAX_VALUE;
         }
         return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 12e55e5..ed4a7bf 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -27,6 +27,7 @@
 import static android.ota.nano.OtaPackageMetadata.ApexMetadata;
 
 import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NONE;
+import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NO_PROVIDER;
 
 import android.annotation.IntDef;
 import android.apex.CompressedApexInfo;
@@ -398,7 +399,13 @@
 
     @VisibleForTesting
     void onSystemServicesReady() {
-        mInjector.getLockSettingsService().setRebootEscrowListener(this);
+        LockSettingsInternal lockSettings = mInjector.getLockSettingsService();
+        if (lockSettings == null) {
+            Slog.e(TAG, "Failed to get lock settings service, skipping set"
+                    + " RebootEscrowListener");
+            return;
+        }
+        lockSettings.setRebootEscrowListener(this);
     }
 
     @Override // Binder call
@@ -564,12 +571,18 @@
             case ROR_NEED_PREPARATION:
                 final long origId = Binder.clearCallingIdentity();
                 try {
-                    boolean result = mInjector.getLockSettingsService().prepareRebootEscrow();
-                    // Clear the RoR preparation state if lock settings reports an failure.
-                    if (!result) {
-                        clearRoRPreparationState();
+                    LockSettingsInternal lockSettings = mInjector.getLockSettingsService();
+                    if (lockSettings == null) {
+                        Slog.e(TAG, "Failed to get lock settings service, skipping"
+                                + " prepareRebootEscrow");
+                        return false;
                     }
-                    return result;
+                    // Clear the RoR preparation state if lock settings reports an failure.
+                    if (!lockSettings.prepareRebootEscrow()) {
+                        clearRoRPreparationState();
+                        return false;
+                    }
+                    return true;
                 } finally {
                     Binder.restoreCallingIdentity(origId);
                 }
@@ -684,7 +697,14 @@
             case ROR_REQUESTED_NEED_CLEAR:
                 final long origId = Binder.clearCallingIdentity();
                 try {
-                    return mInjector.getLockSettingsService().clearRebootEscrow();
+                    LockSettingsInternal lockSettings = mInjector.getLockSettingsService();
+                    if (lockSettings == null) {
+                        Slog.e(TAG, "Failed to get lock settings service, skipping"
+                                + " clearRebootEscrow");
+                        return false;
+                    }
+
+                    return lockSettings.clearRebootEscrow();
                 } finally {
                     Binder.restoreCallingIdentity(origId);
                 }
@@ -778,7 +798,15 @@
         final long origId = Binder.clearCallingIdentity();
         int providerErrorCode;
         try {
-            providerErrorCode = mInjector.getLockSettingsService().armRebootEscrow();
+            LockSettingsInternal lockSettings = mInjector.getLockSettingsService();
+            if (lockSettings == null) {
+                Slog.e(TAG, "Failed to get lock settings service, skipping"
+                        + " armRebootEscrow");
+                return new RebootPreparationError(
+                        RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE,
+                        ARM_REBOOT_ERROR_NO_PROVIDER);
+            }
+            providerErrorCode = lockSettings.armRebootEscrow();
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index e37edeb..d6e7574 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -71,6 +71,7 @@
 import com.android.internal.statusbar.RegisterStatusBarResult;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.GcUtils;
 import com.android.internal.view.AppearanceRegion;
 import com.android.server.LocalServices;
 import com.android.server.UiThread;
@@ -649,6 +650,7 @@
     // ================================================================================
     // From IStatusBarService
     // ================================================================================
+
     @Override
     public void expandNotificationsPanel() {
         enforceExpandStatusBar();
@@ -971,6 +973,22 @@
         return new int[] {disable1, disable2};
     }
 
+    void runGcForTest() {
+        if (!Build.IS_DEBUGGABLE) {
+            throw new SecurityException("runGcForTest requires a debuggable build");
+        }
+
+        // Gc the system along the way
+        GcUtils.runGcAndFinalizersSync();
+
+        if (mBar != null) {
+            try {
+                mBar.runGcForTest();
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
     @Override
     public void setIcon(String slot, String iconPackage, int iconId, int iconLevel,
             String contentDescription) {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
index 6171822..11a4976d 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
@@ -75,6 +75,8 @@
                     return runSendDisableFlag();
                 case "tracing":
                     return runTracing();
+                case "run-gc":
+                    return runGc();
                 // Handle everything that would be handled in `handleDefaultCommand()` explicitly,
                 // so the default can be to pass all args to StatusBar
                 case "-h":
@@ -213,6 +215,11 @@
         return 0;
     }
 
+    private int runGc() {
+        mInterface.runGcForTest();
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index edb042f..f918827 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -50,6 +50,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.VcnManagementService.VcnCallback;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.util.LogUtils;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -305,15 +306,13 @@
                 handleTeardown();
                 break;
             default:
-                Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what);
+                logWtf("Unknown msg.what: " + msg.what);
         }
     }
 
     private void handleConfigUpdated(@NonNull VcnConfig config) {
         // TODO: Add a dump function in VcnConfig that omits PII. Until then, use hashCode()
-        Slog.d(
-                getLogTag(),
-                "Config updated: old = " + mConfig.hashCode() + "; new = " + config.hashCode());
+        logDbg("Config updated: old = " + mConfig.hashCode() + "; new = " + config.hashCode());
 
         mConfig = config;
 
@@ -328,8 +327,7 @@
             // connection details may have changed).
             if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) {
                 if (gatewayConnection == null) {
-                    Slog.wtf(
-                            getLogTag(), "Found gatewayConnectionConfig without GatewayConnection");
+                    logWtf("Found gatewayConnectionConfig without GatewayConnection");
                 } else {
                     gatewayConnection.teardownAsynchronously();
                 }
@@ -342,7 +340,7 @@
     }
 
     private void handleTeardown() {
-        Slog.d(getLogTag(), "Tearing down");
+        logDbg("Tearing down");
         mVcnContext.getVcnNetworkProvider().unregisterListener(mRequestListener);
 
         for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
@@ -353,7 +351,7 @@
     }
 
     private void handleSafeModeStatusChanged() {
-        Slog.d(getLogTag(), "VcnGatewayConnection safe mode status changed");
+        logDbg("VcnGatewayConnection safe mode status changed");
         boolean hasSafeModeGatewayConnection = false;
 
         // If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode
@@ -369,24 +367,19 @@
                 hasSafeModeGatewayConnection ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
         if (oldStatus != mCurrentStatus) {
             mVcnCallback.onSafeModeStatusChanged(hasSafeModeGatewayConnection);
-            Slog.d(
-                    getLogTag(),
+            logDbg(
                     "Safe mode "
                             + (mCurrentStatus == VCN_STATUS_CODE_SAFE_MODE ? "entered" : "exited"));
         }
     }
 
     private void handleNetworkRequested(@NonNull NetworkRequest request) {
-        if (VDBG) {
-            Slog.v(getLogTag(), "Received request " + request);
-        }
+        logVdbg("Received request " + request);
 
         // If preexisting VcnGatewayConnection(s) satisfy request, return
         for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
             if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
-                Slog.d(
-                        getLogTag(),
-                        "Request already satisfied by existing VcnGatewayConnection: " + request);
+                logDbg("Request already satisfied by existing VcnGatewayConnection: " + request);
                 return;
             }
         }
@@ -396,7 +389,7 @@
         for (VcnGatewayConnectionConfig gatewayConnectionConfig :
                 mConfig.getGatewayConnectionConfigs()) {
             if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
-                Slog.d(getLogTag(), "Bringing up new VcnGatewayConnection for request " + request);
+                logDbg("Bringing up new VcnGatewayConnection for request " + request);
 
                 if (getExposedCapabilitiesForMobileDataState(gatewayConnectionConfig).isEmpty()) {
                     // Skip; this network does not provide any services if mobile data is disabled.
@@ -407,8 +400,9 @@
                 // pre-existing VcnGatewayConnections that satisfy a given request, but if state
                 // that affects the satsifying of requests changes, this is theoretically possible.
                 if (mVcnGatewayConnections.containsKey(gatewayConnectionConfig)) {
-                    Slog.wtf(getLogTag(), "Attempted to bring up VcnGatewayConnection for config "
-                            + "with existing VcnGatewayConnection");
+                    logWtf(
+                            "Attempted to bring up VcnGatewayConnection for config "
+                                    + "with existing VcnGatewayConnection");
                     return;
                 }
 
@@ -426,9 +420,7 @@
             }
         }
 
-        if (VDBG) {
-            Slog.v(getLogTag(), "Request could not be fulfilled by VCN: " + request);
-        }
+        logVdbg("Request could not be fulfilled by VCN: " + request);
     }
 
     private Set<Integer> getExposedCapabilitiesForMobileDataState(
@@ -445,7 +437,7 @@
     }
 
     private void handleGatewayConnectionQuit(VcnGatewayConnectionConfig config) {
-        Slog.d(getLogTag(), "VcnGatewayConnection quit: " + config);
+        logDbg("VcnGatewayConnection quit: " + config);
         mVcnGatewayConnections.remove(config);
 
         // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
@@ -480,9 +472,7 @@
                 if (exposedCaps.contains(NET_CAPABILITY_INTERNET)
                         || exposedCaps.contains(NET_CAPABILITY_DUN)) {
                     if (gatewayConnection == null) {
-                        Slog.wtf(
-                                getLogTag(),
-                                "Found gatewayConnectionConfig without GatewayConnection");
+                        logWtf("Found gatewayConnectionConfig without" + " GatewayConnection");
                     } else {
                         // TODO(b/184868850): Optimize by restarting NetworkAgents without teardown.
                         gatewayConnection.teardownAsynchronously();
@@ -493,7 +483,7 @@
             // Trigger re-evaluation of all requests; mobile data state impacts supported caps.
             mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
 
-            Slog.d(getLogTag(), "Mobile data " + (mIsMobileDataEnabled ? "enabled" : "disabled"));
+            logDbg("Mobile data " + (mIsMobileDataEnabled ? "enabled" : "disabled"));
         }
     }
 
@@ -522,8 +512,38 @@
         return request.canBeSatisfiedBy(builder.build());
     }
 
-    private String getLogTag() {
-        return TAG + " [" + mSubscriptionGroup.hashCode() + "]";
+    private String getLogPrefix() {
+        return "[" + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup) + "]: ";
+    }
+
+    private void logVdbg(String msg) {
+        if (VDBG) {
+            Slog.v(TAG, getLogPrefix() + msg);
+        }
+    }
+
+    private void logDbg(String msg) {
+        Slog.d(TAG, getLogPrefix() + msg);
+    }
+
+    private void logDbg(String msg, Throwable tr) {
+        Slog.d(TAG, getLogPrefix() + msg, tr);
+    }
+
+    private void logErr(String msg) {
+        Slog.e(TAG, getLogPrefix() + msg);
+    }
+
+    private void logErr(String msg, Throwable tr) {
+        Slog.e(TAG, getLogPrefix() + msg, tr);
+    }
+
+    private void logWtf(String msg) {
+        Slog.wtf(TAG, getLogPrefix() + msg);
+    }
+
+    private void logWtf(String msg, Throwable tr) {
+        Slog.wtf(TAG, getLogPrefix() + msg, tr);
     }
 
     /**
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 46fd228..5cecff6 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -88,6 +88,7 @@
 import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
 import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
 import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
+import com.android.server.vcn.util.LogUtils;
 import com.android.server.vcn.util.MtuUtils;
 
 import java.io.IOException;
@@ -701,7 +702,7 @@
      * <p>Once torn down, this VcnTunnel CANNOT be started again.
      */
     public void teardownAsynchronously() {
-        Slog.d(TAG, "Triggering async teardown");
+        logDbg("Triggering async teardown");
         sendDisconnectRequestedAndAcquireWakelock(
                 DISCONNECT_REASON_TEARDOWN, true /* shouldQuit */);
 
@@ -711,7 +712,7 @@
 
     @Override
     protected void onQuitting() {
-        Slog.d(TAG, "Quitting VcnGatewayConnection");
+        logDbg("Quitting VcnGatewayConnection");
 
         // No need to call setInterfaceDown(); the IpSecInterface is being fully torn down.
         if (mTunnelIface != null) {
@@ -753,8 +754,7 @@
             // TODO(b/180132994): explore safely removing this Thread check
             mVcnContext.ensureRunningOnLooperThread();
 
-            Slog.d(
-                    TAG,
+            logDbg(
                     "Selected underlying network changed: "
                             + (underlying == null ? null : underlying.network));
 
@@ -783,9 +783,7 @@
         if (!mIsQuitting) {
             mWakeLock.acquire();
 
-            if (VDBG) {
-                Slog.v(TAG, "Wakelock acquired: " + mWakeLock);
-            }
+            logVdbg("Wakelock acquired: " + mWakeLock);
         }
     }
 
@@ -794,9 +792,7 @@
 
         mWakeLock.release();
 
-        if (VDBG) {
-            Slog.v(TAG, "Wakelock released: " + mWakeLock);
-        }
+        logVdbg("Wakelock released: " + mWakeLock);
     }
 
     /**
@@ -814,8 +810,7 @@
 
     @Override
     public void sendMessage(int what) {
-        Slog.wtf(
-                TAG,
+        logWtf(
                 "sendMessage should not be used in VcnGatewayConnection. See"
                         + " sendMessageAndAcquireWakeLock()");
         super.sendMessage(what);
@@ -823,8 +818,7 @@
 
     @Override
     public void sendMessage(int what, Object obj) {
-        Slog.wtf(
-                TAG,
+        logWtf(
                 "sendMessage should not be used in VcnGatewayConnection. See"
                         + " sendMessageAndAcquireWakeLock()");
         super.sendMessage(what, obj);
@@ -832,8 +826,7 @@
 
     @Override
     public void sendMessage(int what, int arg1) {
-        Slog.wtf(
-                TAG,
+        logWtf(
                 "sendMessage should not be used in VcnGatewayConnection. See"
                         + " sendMessageAndAcquireWakeLock()");
         super.sendMessage(what, arg1);
@@ -841,8 +834,7 @@
 
     @Override
     public void sendMessage(int what, int arg1, int arg2) {
-        Slog.wtf(
-                TAG,
+        logWtf(
                 "sendMessage should not be used in VcnGatewayConnection. See"
                         + " sendMessageAndAcquireWakeLock()");
         super.sendMessage(what, arg1, arg2);
@@ -850,8 +842,7 @@
 
     @Override
     public void sendMessage(int what, int arg1, int arg2, Object obj) {
-        Slog.wtf(
-                TAG,
+        logWtf(
                 "sendMessage should not be used in VcnGatewayConnection. See"
                         + " sendMessageAndAcquireWakeLock()");
         super.sendMessage(what, arg1, arg2, obj);
@@ -859,8 +850,7 @@
 
     @Override
     public void sendMessage(Message msg) {
-        Slog.wtf(
-                TAG,
+        logWtf(
                 "sendMessage should not be used in VcnGatewayConnection. See"
                         + " sendMessageAndAcquireWakeLock()");
         super.sendMessage(msg);
@@ -951,15 +941,12 @@
     }
 
     private void setTeardownTimeoutAlarm() {
-        if (VDBG) {
-            Slog.v(TAG, "Setting teardown timeout alarm; mCurrentToken: " + mCurrentToken);
-        }
+        logVdbg("Setting teardown timeout alarm; mCurrentToken: " + mCurrentToken);
 
         // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
         // either case, there is nothing to cancel.
         if (mTeardownTimeoutAlarm != null) {
-            Slog.wtf(
-                    TAG,
+            logWtf(
                     "mTeardownTimeoutAlarm should be null before being set; mCurrentToken: "
                             + mCurrentToken);
         }
@@ -973,9 +960,7 @@
     }
 
     private void cancelTeardownTimeoutAlarm() {
-        if (VDBG) {
-            Slog.v(TAG, "Cancelling teardown timeout alarm; mCurrentToken: " + mCurrentToken);
-        }
+        logVdbg("Cancelling teardown timeout alarm; mCurrentToken: " + mCurrentToken);
 
         if (mTeardownTimeoutAlarm != null) {
             mTeardownTimeoutAlarm.cancel();
@@ -987,12 +972,10 @@
     }
 
     private void setDisconnectRequestAlarm() {
-        if (VDBG) {
-            Slog.v(
-                    TAG,
-                    "Setting alarm to disconnect due to underlying network loss; mCurrentToken: "
-                            + mCurrentToken);
-        }
+        logVdbg(
+                "Setting alarm to disconnect due to underlying network loss;"
+                        + " mCurrentToken: "
+                        + mCurrentToken);
 
         // Only schedule a NEW alarm if none is already set.
         if (mDisconnectRequestAlarm != null) {
@@ -1014,12 +997,10 @@
     }
 
     private void cancelDisconnectRequestAlarm() {
-        if (VDBG) {
-            Slog.v(
-                    TAG,
-                    "Cancelling alarm to disconnect due to underlying network loss; mCurrentToken: "
-                            + mCurrentToken);
-        }
+        logVdbg(
+                "Cancelling alarm to disconnect due to underlying network loss;"
+                        + " mCurrentToken: "
+                        + mCurrentToken);
 
         if (mDisconnectRequestAlarm != null) {
             mDisconnectRequestAlarm.cancel();
@@ -1034,15 +1015,12 @@
     }
 
     private void setRetryTimeoutAlarm(long delay) {
-        if (VDBG) {
-            Slog.v(TAG, "Setting retry alarm; mCurrentToken: " + mCurrentToken);
-        }
+        logVdbg("Setting retry alarm; mCurrentToken: " + mCurrentToken);
 
         // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
         // either case, there is nothing to cancel.
         if (mRetryTimeoutAlarm != null) {
-            Slog.wtf(
-                    TAG,
+            logWtf(
                     "mRetryTimeoutAlarm should be null before being set; mCurrentToken: "
                             + mCurrentToken);
         }
@@ -1052,9 +1030,7 @@
     }
 
     private void cancelRetryTimeoutAlarm() {
-        if (VDBG) {
-            Slog.v(TAG, "Cancel retry alarm; mCurrentToken: " + mCurrentToken);
-        }
+        logVdbg("Cancel retry alarm; mCurrentToken: " + mCurrentToken);
 
         if (mRetryTimeoutAlarm != null) {
             mRetryTimeoutAlarm.cancel();
@@ -1066,9 +1042,7 @@
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     void setSafeModeAlarm() {
-        if (VDBG) {
-            Slog.v(TAG, "Setting safe mode alarm; mCurrentToken: " + mCurrentToken);
-        }
+        logVdbg("Setting safe mode alarm; mCurrentToken: " + mCurrentToken);
 
         // Only schedule a NEW alarm if none is already set.
         if (mSafeModeTimeoutAlarm != null) {
@@ -1084,9 +1058,7 @@
     }
 
     private void cancelSafeModeAlarm() {
-        if (VDBG) {
-            Slog.v(TAG, "Cancel safe mode alarm; mCurrentToken: " + mCurrentToken);
-        }
+        logVdbg("Cancel safe mode alarm; mCurrentToken: " + mCurrentToken);
 
         if (mSafeModeTimeoutAlarm != null) {
             mSafeModeTimeoutAlarm.cancel();
@@ -1152,8 +1124,7 @@
                             + exception.getMessage();
         }
 
-        Slog.d(
-                TAG,
+        logDbg(
                 "Encountered error; code="
                         + errorCode
                         + ", exceptionClass="
@@ -1206,7 +1177,7 @@
             try {
                 enterState();
             } catch (Exception e) {
-                Slog.wtf(TAG, "Uncaught exception", e);
+                logWtf("Uncaught exception", e);
                 sendDisconnectRequestedAndAcquireWakelock(
                         DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
             }
@@ -1238,14 +1209,14 @@
         public final boolean processMessage(Message msg) {
             final int token = msg.arg1;
             if (!isValidToken(token)) {
-                Slog.v(TAG, "Message called with obsolete token: " + token + "; what: " + msg.what);
+                logDbg("Message called with obsolete token: " + token + "; what: " + msg.what);
                 return HANDLED;
             }
 
             try {
                 processStateMsg(msg);
             } catch (Exception e) {
-                Slog.wtf(TAG, "Uncaught exception", e);
+                logWtf("Uncaught exception", e);
                 sendDisconnectRequestedAndAcquireWakelock(
                         DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
             }
@@ -1263,7 +1234,7 @@
             try {
                 exitState();
             } catch (Exception e) {
-                Slog.wtf(TAG, "Uncaught exception", e);
+                logWtf("Uncaught exception", e);
                 sendDisconnectRequestedAndAcquireWakelock(
                         DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
             }
@@ -1303,7 +1274,7 @@
         protected void handleDisconnectRequested(EventDisconnectRequestedInfo info) {
             // TODO(b/180526152): notify VcnStatusCallback for Network loss
 
-            Slog.d(TAG, "Tearing down. Cause: " + info.reason);
+            logDbg("Tearing down. Cause: " + info.reason);
             mIsQuitting = info.shouldQuit;
 
             teardownNetwork();
@@ -1319,7 +1290,7 @@
 
         protected void handleSafeModeTimeoutExceeded() {
             mSafeModeTimeoutAlarm = null;
-            Slog.d(TAG, "Entering safe mode after timeout exceeded");
+            logDbg("Entering safe mode after timeout exceeded");
 
             // Connectivity for this GatewayConnection is broken; tear down the Network.
             teardownNetwork();
@@ -1328,13 +1299,15 @@
         }
 
         protected void logUnexpectedEvent(int what) {
-            Slog.d(TAG, String.format(
-                    "Unexpected event code %d in state %s", what, this.getClass().getSimpleName()));
+            logDbg(
+                    "Unexpected event code "
+                            + what
+                            + " in state "
+                            + this.getClass().getSimpleName());
         }
 
         protected void logWtfUnknownEvent(int what) {
-            Slog.wtf(TAG, String.format(
-                    "Unknown event code %d in state %s", what, this.getClass().getSimpleName()));
+            logWtf("Unknown event code " + what + " in state " + this.getClass().getSimpleName());
         }
     }
 
@@ -1351,7 +1324,7 @@
             }
 
             if (mIkeSession != null || mNetworkAgent != null) {
-                Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState");
+                logWtf("Active IKE Session or NetworkAgent in DisconnectedState");
             }
 
             cancelSafeModeAlarm();
@@ -1419,7 +1392,7 @@
         @Override
         protected void enterState() throws Exception {
             if (mIkeSession == null) {
-                Slog.wtf(TAG, "IKE session was already closed when entering Disconnecting state.");
+                logWtf("IKE session was already closed when entering Disconnecting state.");
                 sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, mCurrentToken);
                 return;
             }
@@ -1506,7 +1479,7 @@
         @Override
         protected void enterState() {
             if (mIkeSession != null) {
-                Slog.wtf(TAG, "ConnectingState entered with active session");
+                logWtf("ConnectingState entered with active session");
 
                 // Attempt to recover.
                 mIkeSession.kill();
@@ -1525,7 +1498,7 @@
 
                     if (oldUnderlying == null) {
                         // This should never happen, but if it does, there's likely a nasty bug.
-                        Slog.wtf(TAG, "Old underlying network was null in connected state. Bug?");
+                        logWtf("Old underlying network was null in connected state. Bug?");
                     }
 
                     // If new underlying is null, all underlying networks have been lost; disconnect
@@ -1620,11 +1593,11 @@
                                 // new NetworkAgent replaces an old one before the unwanted() call
                                 // is processed.
                                 if (mNetworkAgent != agentRef) {
-                                    Slog.d(TAG, "unwanted() called on stale NetworkAgent");
+                                    logDbg("unwanted() called on stale NetworkAgent");
                                     return;
                                 }
 
-                                Slog.d(TAG, "NetworkAgent was unwanted");
+                                logDbg("NetworkAgent was unwanted");
                                 teardownAsynchronously();
                             } /* networkUnwantedCallback */,
                             (status) -> {
@@ -1638,8 +1611,7 @@
                                         setSafeModeAlarm();
                                         break;
                                     default:
-                                        Slog.wtf(
-                                                TAG,
+                                        logWtf(
                                                 "Unknown validation status "
                                                         + status
                                                         + "; ignoring");
@@ -1672,13 +1644,26 @@
                 @NonNull Network underlyingNetwork,
                 @NonNull IpSecTransform transform,
                 int direction) {
+            if (direction != IpSecManager.DIRECTION_IN && direction != IpSecManager.DIRECTION_OUT) {
+                Slog.wtf(TAG, "Applying transform for unexpected direction: " + direction);
+            }
+
             try {
                 tunnelIface.setUnderlyingNetwork(underlyingNetwork);
 
                 // Transforms do not need to be persisted; the IkeSession will keep them alive
                 mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
+
+                // For inbound transforms, additionally allow forwarded traffic to bridge to DUN (as
+                // needed)
+                final Set<Integer> exposedCaps = mConnectionConfig.getAllExposedCapabilities();
+                if (direction == IpSecManager.DIRECTION_IN
+                        && exposedCaps.contains(NET_CAPABILITY_DUN)) {
+                    mIpSecManager.applyTunnelModeTransform(
+                            tunnelIface, IpSecManager.DIRECTION_FWD, transform);
+                }
             } catch (IOException e) {
-                Slog.d(TAG, "Transform application failed for network " + token, e);
+                logDbg("Transform application failed for network " + token, e);
                 sessionLost(token, e);
             }
         }
@@ -1712,7 +1697,7 @@
                     tunnelIface.removeAddress(address.getAddress(), address.getPrefixLength());
                 }
             } catch (IOException e) {
-                Slog.d(TAG, "Adding address to tunnel failed for token " + token, e);
+                logDbg("Adding address to tunnel failed for token " + token, e);
                 sessionLost(token, e);
             }
         }
@@ -1792,7 +1777,7 @@
         }
 
         private void handleMigrationCompleted(EventMigrationCompletedInfo migrationCompletedInfo) {
-            Slog.v(TAG, "Migration completed: " + mUnderlying.network);
+            logDbg("Migration completed: " + mUnderlying.network);
 
             applyTransform(
                     mCurrentToken,
@@ -1816,7 +1801,7 @@
             mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
 
             if (mUnderlying == null) {
-                Slog.v(TAG, "Underlying network lost");
+                logDbg("Underlying network lost");
 
                 // Ignored for now; a new network may be coming up. If none does, the delayed
                 // NETWORK_LOST disconnect will be fired, and tear down the session + network.
@@ -1826,7 +1811,7 @@
             // mUnderlying assumed non-null, given check above.
             // If network changed, migrate. Otherwise, update any existing networkAgent.
             if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) {
-                Slog.v(TAG, "Migrating to new network: " + mUnderlying.network);
+                logDbg("Migrating to new network: " + mUnderlying.network);
                 mIkeSession.setNetwork(mUnderlying.network);
             } else {
                 // oldUnderlying is non-null & underlying network itself has not changed
@@ -1877,7 +1862,7 @@
             mFailedAttempts++;
 
             if (mUnderlying == null) {
-                Slog.wtf(TAG, "Underlying network was null in retry state");
+                logWtf("Underlying network was null in retry state");
                 transitionTo(mDisconnectedState);
             } else {
                 // Safe to blindly set up, as it is cancelled and cleared on exiting this state
@@ -2047,25 +2032,25 @@
 
         @Override
         public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) {
-            Slog.v(TAG, "IkeOpened for token " + mToken);
+            logDbg("IkeOpened for token " + mToken);
             // Nothing to do here.
         }
 
         @Override
         public void onClosed() {
-            Slog.v(TAG, "IkeClosed for token " + mToken);
+            logDbg("IkeClosed for token " + mToken);
             sessionClosed(mToken, null);
         }
 
         @Override
         public void onClosedExceptionally(@NonNull IkeException exception) {
-            Slog.v(TAG, "IkeClosedExceptionally for token " + mToken, exception);
+            logDbg("IkeClosedExceptionally for token " + mToken, exception);
             sessionClosed(mToken, exception);
         }
 
         @Override
         public void onError(@NonNull IkeProtocolException exception) {
-            Slog.v(TAG, "IkeError for token " + mToken, exception);
+            logDbg("IkeError for token " + mToken, exception);
             // Non-fatal, log and continue.
         }
     }
@@ -2082,7 +2067,7 @@
         /** Internal proxy method for injecting of mocked ChildSessionConfiguration */
         @VisibleForTesting(visibility = Visibility.PRIVATE)
         void onOpened(@NonNull VcnChildSessionConfiguration childConfig) {
-            Slog.v(TAG, "ChildOpened for token " + mToken);
+            logDbg("ChildOpened for token " + mToken);
             childOpened(mToken, childConfig);
         }
 
@@ -2093,19 +2078,19 @@
 
         @Override
         public void onClosed() {
-            Slog.v(TAG, "ChildClosed for token " + mToken);
+            logDbg("ChildClosed for token " + mToken);
             sessionLost(mToken, null);
         }
 
         @Override
         public void onClosedExceptionally(@NonNull IkeException exception) {
-            Slog.v(TAG, "ChildClosedExceptionally for token " + mToken, exception);
+            logDbg("ChildClosedExceptionally for token " + mToken, exception);
             sessionLost(mToken, exception);
         }
 
         @Override
         public void onIpSecTransformCreated(@NonNull IpSecTransform transform, int direction) {
-            Slog.v(TAG, "ChildTransformCreated; Direction: " + direction + "; token " + mToken);
+            logDbg("ChildTransformCreated; Direction: " + direction + "; token " + mToken);
             childTransformCreated(mToken, transform, direction);
         }
 
@@ -2113,7 +2098,7 @@
         public void onIpSecTransformsMigrated(
                 @NonNull IpSecTransform inIpSecTransform,
                 @NonNull IpSecTransform outIpSecTransform) {
-            Slog.v(TAG, "ChildTransformsMigrated; token " + mToken);
+            logDbg("ChildTransformsMigrated; token " + mToken);
             migrationCompleted(mToken, inIpSecTransform, outIpSecTransform);
         }
 
@@ -2121,10 +2106,48 @@
         public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) {
             // Nothing to be done; no references to the IpSecTransform are held, and this transform
             // will be closed by the IKE library.
-            Slog.v(TAG, "ChildTransformDeleted; Direction: " + direction + "; for token " + mToken);
+            logDbg("ChildTransformDeleted; Direction: " + direction + "; for token " + mToken);
         }
     }
 
+    private String getLogPrefix() {
+        return "["
+                + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
+                + "-"
+                + mConnectionConfig.getGatewayConnectionName()
+                + "]: ";
+    }
+
+    private void logVdbg(String msg) {
+        if (VDBG) {
+            Slog.v(TAG, getLogPrefix() + msg);
+        }
+    }
+
+    private void logDbg(String msg) {
+        Slog.d(TAG, getLogPrefix() + msg);
+    }
+
+    private void logDbg(String msg, Throwable tr) {
+        Slog.d(TAG, getLogPrefix() + msg, tr);
+    }
+
+    private void logErr(String msg) {
+        Slog.e(TAG, getLogPrefix() + msg);
+    }
+
+    private void logErr(String msg, Throwable tr) {
+        Slog.e(TAG, getLogPrefix() + msg, tr);
+    }
+
+    private void logWtf(String msg) {
+        Slog.wtf(TAG, getLogPrefix() + msg);
+    }
+
+    private void logWtf(String msg, Throwable tr) {
+        Slog.wtf(TAG, getLogPrefix() + msg, tr);
+    }
+
     /**
      * Dumps the state of this VcnGatewayConnection for logging and debugging purposes.
      *
diff --git a/services/core/java/com/android/server/vcn/util/LogUtils.java b/services/core/java/com/android/server/vcn/util/LogUtils.java
new file mode 100644
index 0000000..93728ce
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/util/LogUtils.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.vcn.util;
+
+import android.annotation.Nullable;
+import android.os.ParcelUuid;
+
+import com.android.internal.util.HexDump;
+
+/** @hide */
+public class LogUtils {
+    /**
+     * Returns the hash of the subscription group in hexadecimal format.
+     *
+     * @return the hexadecimal encoded string if uuid was non-null, else {@code null}
+     */
+    @Nullable
+    public static String getHashedSubscriptionGroup(@Nullable ParcelUuid uuid) {
+        if (uuid == null) {
+            return null;
+        }
+
+        return HexDump.toHexString(uuid.hashCode());
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8f3702a..9f51d97 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -115,8 +115,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
@@ -325,7 +323,6 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.XmlUtils;
-import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
@@ -2385,7 +2382,8 @@
         return occludesParent(false /* includingFinishing */);
     }
 
-    private boolean occludesParent(boolean includingFinishing) {
+    @VisibleForTesting
+    boolean occludesParent(boolean includingFinishing) {
         if (!includingFinishing && finishing) {
             return false;
         }
@@ -2843,7 +2841,6 @@
 
             final boolean endTask = task.getTopNonFinishingActivity() == null
                     && !task.isClearingToReuseTask();
-            final int transit = endTask ? TRANSIT_OLD_TASK_CLOSE : TRANSIT_OLD_ACTIVITY_CLOSE;
             if (newTransition != null) {
                 mAtmService.getTransitionController().requestStartTransition(newTransition,
                         endTask ? task : null, null /* remote */);
@@ -2900,7 +2897,7 @@
             } else if (!isState(PAUSING)) {
                 if (mVisibleRequested) {
                     // Prepare and execute close transition.
-                    prepareActivityHideTransitionAnimation(transit);
+                    prepareActivityHideTransitionAnimation();
                 }
 
                 final boolean removedActivity = completeFinishing("finishIfPossible") == null;
@@ -2918,11 +2915,9 @@
                 // In this case, we can set the visibility of all the task overlay activities when
                 // we detect the last one is finishing to keep them in sync.
                 if (task.onlyHasTaskOverlayActivities(false /* includeFinishing */)) {
-                    final PooledConsumer c = PooledLambda.obtainConsumer(
-                            ActivityRecord::prepareActivityHideTransitionAnimationIfOvarlay,
-                            PooledLambda.__(ActivityRecord.class), transit);
-                    task.forAllActivities(c);
-                    c.recycle();
+                    task.forAllActivities((r) -> {
+                        r.prepareActivityHideTransitionAnimationIfOvarlay();
+                    });
                 }
                 return removedActivity ? FINISH_RESULT_REMOVED : FINISH_RESULT_REQUESTED;
             } else {
@@ -2935,28 +2930,33 @@
         }
     }
 
-    private void prepareActivityHideTransitionAnimationIfOvarlay(@TransitionOldType int transit) {
+    private void prepareActivityHideTransitionAnimationIfOvarlay() {
         if (mTaskOverlay) {
-            prepareActivityHideTransitionAnimation(transit);
+            prepareActivityHideTransitionAnimation();
         }
     }
 
-    private void prepareActivityHideTransitionAnimation(@TransitionOldType int transit) {
+    private void prepareActivityHideTransitionAnimation() {
         final DisplayContent dc = mDisplayContent;
         dc.prepareAppTransition(TRANSIT_CLOSE);
         setVisibility(false);
         dc.executeAppTransition();
     }
 
+    ActivityRecord completeFinishing(String reason) {
+        return completeFinishing(true /* updateVisibility */, reason);
+    }
+
     /**
      * Complete activity finish request that was initiated earlier. If the activity is still
      * pausing we will wait for it to complete its transition. If the activity that should appear in
      * place of this one is not visible yet - we'll wait for it first. Otherwise - activity can be
      * destroyed right away.
+     * @param updateVisibility Indicate if need to update activity visibility.
      * @param reason Reason for finishing the activity.
      * @return Flag indicating whether the activity was removed from history.
      */
-    ActivityRecord completeFinishing(String reason) {
+    ActivityRecord completeFinishing(boolean updateVisibility, String reason) {
         if (!finishing || isState(RESUMED)) {
             throw new IllegalArgumentException(
                     "Activity must be finishing and not resumed to complete, r=" + this
@@ -2968,13 +2968,11 @@
             return this;
         }
 
-        final boolean isCurrentVisible = mVisibleRequested || isState(PAUSED);
-        if (isCurrentVisible) {
-            final Task rootTask = getRootTask();
-            final ActivityRecord activity = rootTask.getResumedActivity();
+        final boolean isCurrentVisible = mVisibleRequested || isState(PAUSED, STARTED);
+        if (updateVisibility && isCurrentVisible) {
             boolean ensureVisibility = false;
-            if (activity != null && !activity.occludesParent()) {
-                // If the resume activity is not opaque, we need to make sure the visibilities of
+            if (occludesParent(true /* includingFinishing */)) {
+                // If the current activity is not opaque, we need to make sure the visibilities of
                 // activities be updated, they may be seen by users.
                 ensureVisibility = true;
             } else if (mTaskSupervisor.getKeyguardController().isKeyguardLocked()
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 411059d..206f67e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2449,6 +2449,10 @@
         setWindowingMode(windowingMode);
     }
 
+    /**
+     * See {@code WindowState#applyImeWindowsIfNeeded} for the details that we won't traverse the
+     * IME window in some cases.
+     */
     boolean forAllImeWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
         return mImeWindowsContainer.forAllWindowForce(callback, traverseTopToBottom);
     }
@@ -4566,6 +4570,8 @@
         private boolean skipImeWindowsDuringTraversal(DisplayContent dc) {
             // We skip IME windows so they're processed just above their target, except
             // in split-screen mode where we process the IME containers above the docked divider.
+            // Note that this method check should align with {@link
+            // WindowState#applyImeWindowsIfNeeded} in case of any state mismatch.
             return dc.getImeTarget(IME_TARGET_LAYERING) != null
                     && !dc.getDefaultTaskDisplayArea().isSplitScreenModeActivated();
         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e5e1a7a..be01173 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5842,8 +5842,13 @@
             final boolean wasStopping = prev.isState(STOPPING);
             prev.setState(PAUSED, "completePausedLocked");
             if (prev.finishing) {
+                // We will update the activity visibility later, no need to do in
+                // completeFinishing(). Updating visibility here might also making the next
+                // activities to be resumed, and could result in wrong app transition due to
+                // lack of previous activity information.
                 ProtoLog.v(WM_DEBUG_STATES, "Executing finish of activity: %s", prev);
-                prev = prev.completeFinishing("completePausedLocked");
+                prev = prev.completeFinishing(false /* updateVisibility */,
+                        "completePausedLocked");
             } else if (prev.hasProcess()) {
                 ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
                         + "wasStopping=%b visibleRequested=%b",  prev,  wasStopping,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b40223f..540035f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4842,7 +4842,10 @@
         // directly above it. The exception is if we are in split screen
         // in which case we process the IME at the DisplayContent level to
         // ensure it is above the docked divider.
-        if (isImeLayeringTarget() && !inSplitScreenWindowingMode()) {
+        // (i.e. Like {@link DisplayContent.ImeContainer#skipImeWindowsDuringTraversal}, the IME
+        // window will be ignored to traverse when the IME target is still in split-screen mode).
+        if (isImeLayeringTarget()
+                && !getDisplayContent().getDefaultTaskDisplayArea().isSplitScreenModeActivated()) {
             if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) {
                 return true;
             }
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index 6721ecf..bfd8005 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -66,7 +66,7 @@
 
     android::sp<IStats> statsHal = new StatsHal();
     const android::status_t err = statsHal->registerAsService();
-    LOG_ALWAYS_FATAL_IF(err != android::OK, "Cannot register HIDL %s: %d", IStats::descriptor, err);
+    ALOGW_IF(err != android::OK, "Cannot register HIDL %s: %d", IStats::descriptor, err);
 }
 
 } // namespace
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ef7360d..23a67b7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -28,6 +28,7 @@
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
+import static android.app.admin.DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED;
 import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY;
 import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE;
 import static android.app.admin.DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED;
@@ -883,12 +884,6 @@
                 synchronized (getLockObject()) {
                     // Check whether the user is affiliated, *before* removing its data.
                     boolean isRemovedUserAffiliated = isUserAffiliatedWithDeviceLocked(userHandle);
-                    if (isProfileOwnerOfOrganizationOwnedDevice(userHandle)) {
-                        // Disable network and security logging
-                        mInjector.securityLogSetLoggingEnabledProperty(false);
-                        mSecurityLogMonitor.stop();
-                        setNetworkLoggingActiveInternal(false);
-                    }
                     removeUserData(userHandle);
                     if (!isRemovedUserAffiliated) {
                         // We discard the logs when unaffiliated users are deleted (so that the
@@ -1779,6 +1774,7 @@
     }
 
     void removeUserData(int userHandle) {
+        final boolean isOrgOwned;
         synchronized (getLockObject()) {
             if (userHandle == UserHandle.USER_SYSTEM) {
                 Slogf.w(LOG_TAG, "Tried to remove device policy file for user 0! Ignoring.");
@@ -1786,6 +1782,9 @@
             }
             updatePasswordQualityCacheForUserGroup(userHandle);
             mPolicyCache.onUserRemoved(userHandle);
+
+            isOrgOwned = mOwners.isProfileOwnerOfOrganizationOwnedDevice(userHandle);
+
             mOwners.removeProfileOwner(userHandle);
             mOwners.writeProfileOwner(userHandle);
 
@@ -1799,6 +1798,14 @@
             policyFile.delete();
             Slogf.i(LOG_TAG, "Removed device policy file " + policyFile.getAbsolutePath());
         }
+        if (isOrgOwned) {
+            final UserInfo primaryUser = mUserManager.getPrimaryUser();
+            if (primaryUser != null) {
+                clearOrgOwnedProfileOwnerDeviceWidePolicies(primaryUser.id);
+            } else {
+                Slogf.wtf(LOG_TAG, "Was unable to get primary user.");
+            }
+        }
     }
 
     /**
@@ -3537,6 +3544,7 @@
                 "Caller must be shell or hold MANAGE_PROFILE_AND_DEVICE_OWNERS to call "
                         + "forceRemoveActiveAdmin");
         mInjector.binderWithCleanCallingIdentity(() -> {
+            boolean isOrgOwnedProfile = false;
             synchronized (getLockObject()) {
                 if (!isAdminTestOnlyLocked(adminReceiver, userHandle)) {
                     throw new SecurityException("Attempt to remove non-test admin "
@@ -3548,13 +3556,7 @@
                     clearDeviceOwnerLocked(getDeviceOwnerAdminLocked(), userHandle);
                 }
                 if (isProfileOwner(adminReceiver, userHandle)) {
-                    if (isProfileOwnerOfOrganizationOwnedDevice(userHandle)) {
-                        UserHandle parentUserHandle = UserHandle.of(getProfileParentId(userHandle));
-                        mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
-                                false, parentUserHandle);
-                        mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER,
-                                false, parentUserHandle);
-                    }
+                    isOrgOwnedProfile = isProfileOwnerOfOrganizationOwnedDevice(userHandle);
                     final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver,
                             userHandle, /* parent */ false);
                     clearProfileOwnerLocked(admin, userHandle);
@@ -3562,11 +3564,26 @@
             }
             // Remove the admin skipping sending the broadcast.
             removeAdminArtifacts(adminReceiver, userHandle);
+
+            // In case of PO on org owned device, clean device-wide policies and restrictions.
+            if (isOrgOwnedProfile) {
+                final UserHandle parentUser = UserHandle.of(getProfileParentId(userHandle));
+                clearOrgOwnedProfileOwnerUserRestrictions(parentUser);
+                clearOrgOwnedProfileOwnerDeviceWidePolicies(parentUser.getIdentifier());
+            }
+
             Slogf.i(LOG_TAG, "Admin " + adminReceiver + " removed from user " + userHandle);
         });
     }
 
-    private void clearDeviceOwnerUserRestrictionLocked(UserHandle userHandle) {
+    private void clearOrgOwnedProfileOwnerUserRestrictions(UserHandle parentUserHandle) {
+        mUserManager.setUserRestriction(
+                UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, false, parentUserHandle);
+        mUserManager.setUserRestriction(
+                UserManager.DISALLOW_ADD_USER, false, parentUserHandle);
+    }
+
+    private void clearDeviceOwnerUserRestriction(UserHandle userHandle) {
         // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the original state
         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) {
             mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, userHandle);
@@ -6710,25 +6727,16 @@
                 // when wipeData is _not_ called on the parent instance, it implies relinquishing
                 // control over the device, wiping only the work profile. So the user restriction
                 // on profile removal needs to be removed first.
-
-                mInjector.binderWithCleanCallingIdentity(() -> {
-                    // Clear restriction as user.
-                    mUserManager.setUserRestriction(
-                            UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, false,
-                            UserHandle.SYSTEM);
-                    mUserManager.setUserRestriction(
-                            UserManager.DISALLOW_ADD_USER, false, UserHandle.SYSTEM);
-
-                    // Device-wide policies set by the profile owner need to be cleaned up here.
-                    mLockPatternUtils.setDeviceOwnerInfo(null);
-                });
+                final UserHandle parentUser = UserHandle.of(getProfileParentId(userId));
+                mInjector.binderWithCleanCallingIdentity(
+                        () -> clearOrgOwnedProfileOwnerUserRestrictions(parentUser));
             }
         }
         DevicePolicyEventLogger event = DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.WIPE_DATA_WITH_REASON)
                 .setInt(flags)
-                .setStrings(calledOnParentInstance ? CALLED_FROM_PARENT : NOT_CALLED_FROM_PARENT)
-                ;
+                .setStrings(calledOnParentInstance ? CALLED_FROM_PARENT : NOT_CALLED_FROM_PARENT);
+
         final String adminName;
         final ComponentName adminComp;
         if (admin != null) {
@@ -6755,6 +6763,55 @@
         wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId);
     }
 
+    /**
+     * Clears device wide policies enforced by COPE PO when relinquishing the device. This method
+     * should be invoked once the admin is gone, so that all methods that rely on calculating
+     * aggregate policy (e.g. strong auth timeout) from all admins aren't affected by its policies.
+     * This method assumes that there is no other device or profile owners left on the device.
+     * Shouldn't be called from binder thread without clearing identity.
+     */
+    private void clearOrgOwnedProfileOwnerDeviceWidePolicies(@UserIdInt int parentId) {
+        Slogf.i(LOG_TAG, "Cleaning up device-wide policies left over from org-owned profile...");
+        // Lockscreen message
+        mLockPatternUtils.setDeviceOwnerInfo(null);
+        // Wifi config lockdown
+        mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
+        // Security logging
+        if (mInjector.securityLogGetLoggingEnabledProperty()) {
+            mSecurityLogMonitor.stop();
+            mInjector.securityLogSetLoggingEnabledProperty(false);
+        }
+        // Network logging
+        setNetworkLoggingActiveInternal(false);
+
+        // System update policy.
+        final boolean hasSystemUpdatePolicy;
+        synchronized (getLockObject()) {
+            hasSystemUpdatePolicy = mOwners.getSystemUpdatePolicy() != null;
+            if (hasSystemUpdatePolicy) {
+                mOwners.clearSystemUpdatePolicy();
+                mOwners.writeDeviceOwner();
+            }
+        }
+        if (hasSystemUpdatePolicy) {
+            mContext.sendBroadcastAsUser(
+                    new Intent(ACTION_SYSTEM_UPDATE_POLICY_CHANGED), UserHandle.SYSTEM);
+        }
+
+        // Unsuspend personal apps if needed.
+        suspendPersonalAppsInternal(parentId, false);
+
+        // Notify FRP agent, LSS and WindowManager to ensure they don't hold on to stale policies.
+        final int frpAgentUid = getFrpManagementAgentUid();
+        if (frpAgentUid > 0) {
+            notifyResetProtectionPolicyChanged(frpAgentUid);
+        }
+        mLockSettingsInternal.refreshStrongAuthTimeout(parentId);
+        updateScreenCaptureDisabled(parentId, getScreenCaptureDisabled(null, parentId, false));
+
+        Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
+    }
+
     private void wipeDataNoLock(ComponentName admin, int flags, String internalReason,
                                 String wipeReasonForUser, int userId) {
         wtfIfInLock();
@@ -6822,13 +6879,8 @@
             saveSettingsLocked(caller.getUserId());
         }
 
-        final Intent intent = new Intent(
-                DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED).addFlags(
-                Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_RECEIVER_FOREGROUND);
-
-        mInjector.binderWithCleanCallingIdentity(() -> mContext.sendBroadcastAsUser(intent,
-                UserHandle.getUserHandleForUid(frpManagementAgentUid),
-                android.Manifest.permission.MANAGE_FACTORY_RESET_PROTECTION));
+        mInjector.binderWithCleanCallingIdentity(
+                () -> notifyResetProtectionPolicyChanged(frpManagementAgentUid));
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_FACTORY_RESET_PROTECTION)
@@ -6836,6 +6888,16 @@
                 .write();
     }
 
+    // Shouldn't be called from binder thread without clearing identity.
+    private void notifyResetProtectionPolicyChanged(int frpManagementAgentUid) {
+        final Intent intent = new Intent(
+                DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED).addFlags(
+                Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendBroadcastAsUser(intent,
+                UserHandle.getUserHandleForUid(frpManagementAgentUid),
+                permission.MANAGE_FACTORY_RESET_PROTECTION);
+    }
+
     @Override
     public FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(
             @Nullable ComponentName who) {
@@ -8506,7 +8568,7 @@
         mOwners.writeDeviceOwner();
         updateDeviceOwnerLocked();
 
-        clearDeviceOwnerUserRestrictionLocked(UserHandle.of(userId));
+        clearDeviceOwnerUserRestriction(UserHandle.of(userId));
         mInjector.securityLogSetLoggingEnabledProperty(false);
         mSecurityLogMonitor.stop();
         setNetworkLoggingActiveInternal(false);
@@ -12936,8 +12998,7 @@
             mOwners.writeDeviceOwner();
         }
         mInjector.binderWithCleanCallingIdentity(() -> mContext.sendBroadcastAsUser(
-                new Intent(DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED),
-                UserHandle.SYSTEM));
+                new Intent(ACTION_SYSTEM_UPDATE_POLICY_CHANGED), UserHandle.SYSTEM));
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_SYSTEM_UPDATE_POLICY)
                 .setAdmin(who)
@@ -14513,14 +14574,10 @@
      * apps.
      */
     @Override
-    public void forceUpdateUserSetupComplete() {
-        final CallerIdentity caller = getCallerIdentity();
+    public void forceUpdateUserSetupComplete(@UserIdInt int userId) {
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
-        Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
-                "Caller has to be in user 0");
 
-        final int userId = UserHandle.USER_SYSTEM;
         boolean isUserCompleted = mInjector.settingsSecureGetIntForUser(
                 Settings.Secure.USER_SETUP_COMPLETE, 0, userId) != 0;
         DevicePolicyData policy = getUserData(userId);
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
index 5c7ccfc..2d03981 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
@@ -265,4 +265,36 @@
         assertThat(sStats.getDocumentRetrievingLatencyMillis())
                 .isEqualTo(nativeDocumentRetrievingLatencyMillis);
     }
+
+    @Test
+    public void testAppSearchStats_SetSchemaStats() {
+        int nativeLatencyMillis = 1;
+        int newTypeCount = 2;
+        int compatibleTypeChangeCount = 3;
+        int indexIncompatibleTypeChangeCount = 4;
+        int backwardsIncompatibleTypeChangeCount = 5;
+        final SetSchemaStats sStats =
+                new SetSchemaStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE)
+                        .setStatusCode(TEST_STATUS_CODE)
+                        .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS)
+                        .setNativeLatencyMillis(nativeLatencyMillis)
+                        .setNewTypeCount(newTypeCount)
+                        .setCompatibleTypeChangeCount(compatibleTypeChangeCount)
+                        .setIndexIncompatibleTypeChangeCount(indexIncompatibleTypeChangeCount)
+                        .setBackwardsIncompatibleTypeChangeCount(
+                                backwardsIncompatibleTypeChangeCount)
+                        .build();
+
+        assertThat(sStats.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+        assertThat(sStats.getDatabase()).isEqualTo(TEST_DATA_BASE);
+        assertThat(sStats.getStatusCode()).isEqualTo(TEST_STATUS_CODE);
+        assertThat(sStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS);
+        assertThat(sStats.getNativeLatencyMillis()).isEqualTo(nativeLatencyMillis);
+        assertThat(sStats.getNewTypeCount()).isEqualTo(newTypeCount);
+        assertThat(sStats.getCompatibleTypeChangeCount()).isEqualTo(compatibleTypeChangeCount);
+        assertThat(sStats.getIndexIncompatibleTypeChangeCount())
+                .isEqualTo(indexIncompatibleTypeChangeCount);
+        assertThat(sStats.getBackwardsIncompatibleTypeChangeCount())
+                .isEqualTo(backwardsIncompatibleTypeChangeCount);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index c54dffc..2270943 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -41,6 +41,7 @@
 import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_PROFILE_OFF_DEADLINE;
 import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_TURN_PROFILE_ON_NOTIFICATION;
 import static com.android.server.devicepolicy.DpmMockContext.CALLER_USER_HANDLE;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.testutils.TestUtils.assertExpectException;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -82,6 +83,7 @@
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.PasswordMetrics;
+import android.app.admin.SystemUpdatePolicy;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -3888,37 +3890,28 @@
     public void testForceUpdateUserSetupComplete_permission() {
         // GIVEN the permission MANAGE_PROFILE_AND_DEVICE_OWNERS is not granted
         assertExpectException(SecurityException.class, /* messageRegex =*/ null,
-                () -> dpm.forceUpdateUserSetupComplete());
-    }
-
-    @Test
-    public void testForceUpdateUserSetupComplete_systemUser() {
-        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
-        // GIVEN calling from user 20
-        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
-        assertExpectException(SecurityException.class, /* messageRegex =*/ null,
-                () -> dpm.forceUpdateUserSetupComplete());
+                () -> dpm.forceUpdateUserSetupComplete(UserHandle.USER_SYSTEM));
     }
 
     @Test
     public void testForceUpdateUserSetupComplete_forcesUpdate() {
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        final int userId = UserHandle.getUserId(mContext.binder.callingUid);
 
-        final int userId = UserHandle.USER_SYSTEM;
         // GIVEN userComplete is false in SettingsProvider
         setUserSetupCompleteForUser(false, userId);
 
         // GIVEN userComplete is true in DPM
         DevicePolicyData userData = new DevicePolicyData(userId);
         userData.mUserSetupComplete = true;
-        dpms.mUserData.put(UserHandle.USER_SYSTEM, userData);
+        dpms.mUserData.put(userId, userData);
 
         assertThat(dpms.hasUserSetupCompleted()).isTrue();
 
-        dpm.forceUpdateUserSetupComplete();
+        dpm.forceUpdateUserSetupComplete(userId);
 
-        // THEN the state in dpms is not changed
+        // THEN the state in dpms is changed
         assertThat(dpms.hasUserSetupCompleted()).isFalse();
     }
 
@@ -3948,10 +3941,9 @@
 
         // Enabling logging should not change the timestamp.
         dpm.setSecurityLoggingEnabled(admin1, true);
-        verify(getServices().settings)
-                .securityLogSetLoggingEnabledProperty(true);
-        when(getServices().settings.securityLogGetLoggingEnabledProperty())
-                .thenReturn(true);
+        verify(getServices().settings).securityLogSetLoggingEnabledProperty(true);
+
+        when(getServices().settings.securityLogGetLoggingEnabledProperty()).thenReturn(true);
         assertThat(dpm.getLastSecurityLogRetrievalTime()).isEqualTo(-1);
 
         // Retrieving the logs should update the timestamp.
@@ -4776,8 +4768,8 @@
         when(getServices().iactivityManager.getCurrentUser())
                 .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
         // Get mock reason string since we throw an IAE with empty string input.
-        when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
-                thenReturn("Just a test string.");
+        when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe))
+                .thenReturn("Just a test string.");
 
         dpm.wipeData(0);
         verify(getServices().userManagerInternal).removeUserEvenWhenDisallowed(
@@ -4785,6 +4777,68 @@
     }
 
     @Test
+    public void testWipeDataManagedProfileOnOrganizationOwnedDevice() throws Exception {
+        setupProfileOwner();
+        configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
+
+        // Even if the caller is the managed profile, the current user is the user 0
+        when(getServices().iactivityManager.getCurrentUser())
+                .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+        // Get mock reason string since we throw an IAE with empty string input.
+        when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe))
+                .thenReturn("Just a test string.");
+        when(getServices().userManager.getProfileParent(CALLER_USER_HANDLE))
+                .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+        when(getServices().userManager.getPrimaryUser())
+                .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+
+        // Set some device-wide policies:
+        // Security logging
+        when(getServices().settings.securityLogGetLoggingEnabledProperty()).thenReturn(true);
+        // System update policy
+        dpms.mOwners.setSystemUpdatePolicy(SystemUpdatePolicy.createAutomaticInstallPolicy());
+        // Make it look as if FRP agent is present.
+        when(dpms.mMockInjector.getPersistentDataBlockManagerInternal().getAllowedUid())
+                .thenReturn(12345 /* some UID in user 0 */);
+        // Make personal apps look suspended
+        dpms.getUserData(UserHandle.USER_SYSTEM).mAppsSuspended = true;
+
+        clearInvocations(getServices().iwindowManager);
+
+        dpm.wipeData(0);
+        verify(getServices().userManagerInternal).removeUserEvenWhenDisallowed(CALLER_USER_HANDLE);
+
+        // Make sure COPE restrictions are lifted:
+        verify(getServices().userManager).setUserRestriction(
+                UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, false, UserHandle.SYSTEM);
+        verify(getServices().userManager).setUserRestriction(
+                UserManager.DISALLOW_ADD_USER, false, UserHandle.SYSTEM);
+
+        // Some device-wide policies are getting cleaned-up after the user is removed.
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        sendBroadcastWithUser(dpms, Intent.ACTION_USER_REMOVED, CALLER_USER_HANDLE);
+
+        // Screenlock info should be removed
+        verify(getServices().lockPatternUtils).setDeviceOwnerInfo(null);
+        // Wifi config lockdown should be lifted
+        verify(getServices().settings).settingsGlobalPutInt(
+                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
+        // System update policy should be removed
+        assertThat(dpms.mOwners.getSystemUpdatePolicy()).isNull();
+        // FRP agent should be notified
+        verify(mContext.spiedContext, times(0)).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+                MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
+        // Refresh strong auth timeout and screen capture
+        verify(getServices().lockSettingsInternal).refreshStrongAuthTimeout(UserHandle.USER_SYSTEM);
+        verify(getServices().iwindowManager).refreshScreenCaptureDisabled(UserHandle.USER_SYSTEM);
+        // Unsuspend personal apps
+        verify(getServices().packageManagerInternal)
+                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, UserHandle.USER_SYSTEM);
+    }
+
+    @Test
     public void testWipeDataManagedProfileDisallowed() throws Exception {
         final int MANAGED_PROFILE_USER_ID = 15;
         final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
index e2b6a99..41946fb 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
@@ -26,6 +26,8 @@
 
 import com.android.internal.R;
 
+import java.util.HashMap;
+
 /**
  * Fake class which stubs default system configuration with user-configurable
  * settings (useful for testing).
@@ -33,6 +35,8 @@
 final class FakeHdmiCecConfig extends HdmiCecConfig {
     private static final String TAG = "FakeHdmiCecConfig";
 
+    private final HashMap<String, String> mSettings = new HashMap<>();
+
     public static Context buildContext(Context context) {
         Context contextSpy = spy(new ContextWrapper(context));
         doReturn(buildResources(context)).when(contextSpy).getResources();
@@ -218,4 +222,15 @@
     FakeHdmiCecConfig(@NonNull Context context) {
         super(buildContext(context), new StorageAdapter(context));
     }
+
+    @Override
+    protected String retrieveValue(@NonNull Setting setting, @NonNull String defaultValue) {
+        return mSettings.getOrDefault(setting.getName(), defaultValue);
+    }
+
+    @Override
+    protected void storeValue(@NonNull Setting setting, @NonNull String value) {
+        mSettings.put(setting.getName(), value);
+        notifySettingChanged(setting);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 68aa96a..b1d77d0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -47,7 +47,6 @@
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -237,9 +236,6 @@
         mHdmiPortInfo[3] =
             new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false);
         mNativeWrapper.setPortInfo(mHdmiPortInfo);
-        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
-                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
-                HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
         mHdmiControlServiceSpy.initService();
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
 
@@ -362,23 +358,6 @@
     }
 
     @Test
-    public void setAndGetCecVolumeControlEnabled_changesSetting() {
-        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
-                HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
-                HdmiControlManager.VOLUME_CONTROL_DISABLED);
-        assertThat(mHdmiControlServiceSpy.readIntSetting(
-                Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, -1)).isEqualTo(
-                HdmiControlManager.VOLUME_CONTROL_DISABLED);
-
-        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
-                HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
-                HdmiControlManager.VOLUME_CONTROL_ENABLED);
-        assertThat(mHdmiControlServiceSpy.readIntSetting(
-                Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, -1)).isEqualTo(
-                HdmiControlManager.VOLUME_CONTROL_ENABLED);
-    }
-
-    @Test
     public void setAndGetCecVolumeControlEnabledInternal_doesNotChangeSetting() {
         mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 3581206..4a09cf8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -50,12 +50,14 @@
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.GenericDocument;
-import android.app.appsearch.IAppSearchBatchResultCallback;
-import android.app.appsearch.IAppSearchManager;
-import android.app.appsearch.IAppSearchResultCallback;
 import android.app.appsearch.PackageIdentifier;
 import android.app.appsearch.SearchResultPage;
 import android.app.appsearch.SetSchemaResponse;
+import android.app.appsearch.aidl.AppSearchBatchResultParcel;
+import android.app.appsearch.aidl.AppSearchResultParcel;
+import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
+import android.app.appsearch.aidl.IAppSearchManager;
+import android.app.appsearch.aidl.IAppSearchResultCallback;
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ActivityNotFoundException;
@@ -675,7 +677,9 @@
                 }
             }
             final SetSchemaResponse response = new SetSchemaResponse.Builder().build();
-            callback.onResult(AppSearchResult.newSuccessfulResult(response.getBundle()));
+            callback.onResult(
+                    new AppSearchResultParcel(
+                            AppSearchResult.newSuccessfulResult(response.getBundle())));
         }
 
         @Override
@@ -711,7 +715,7 @@
                 }
                 docMap.put(doc.getId(), doc);
             }
-            callback.onResult(builder.build());
+            callback.onResult(new AppSearchBatchResultParcel<>(builder.build()));
         }
 
         @Override
@@ -737,7 +741,7 @@
                     }
                 }
             }
-            callback.onResult(builder.build());
+            callback.onResult(new AppSearchBatchResultParcel<>(builder.build()));
         }
 
         @Override
@@ -749,7 +753,8 @@
                 final Bundle page = new Bundle();
                 page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
                 page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
-                callback.onResult(AppSearchResult.newSuccessfulResult(page));
+                callback.onResult(
+                        new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(page)));
                 return;
             }
             final List<GenericDocument> documents = new ArrayList<>(mDocumentMap.get(key).values());
@@ -765,7 +770,8 @@
                 resultBundles.add(resultBundle);
             }
             page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
-            callback.onResult(AppSearchResult.newSuccessfulResult(page));
+            callback.onResult(
+                    new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(page)));
         }
 
         @Override
@@ -780,7 +786,8 @@
             final Bundle page = new Bundle();
             page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
             page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
-            callback.onResult(AppSearchResult.newSuccessfulResult(page));
+            callback.onResult(
+                    new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(page)));
         }
 
         @Override
@@ -835,7 +842,7 @@
                     }
                 }
             }
-            callback.onResult(builder.build());
+            callback.onResult(new AppSearchBatchResultParcel<>(builder.build()));
         }
 
         @Override
@@ -844,11 +851,13 @@
                 throws RemoteException {
             final String key = getKey(userId, databaseName);
             if (!mDocumentMap.containsKey(key)) {
-                callback.onResult(AppSearchResult.newSuccessfulResult(null));
+                callback.onResult(
+                        new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(null)));
                 return;
             }
             mDocumentMap.get(key).clear();
-            callback.onResult(AppSearchResult.newSuccessfulResult(null));
+            callback.onResult(
+                    new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(null)));
         }
 
         @Override
@@ -878,7 +887,8 @@
         }
 
         private void ignore(IAppSearchResultCallback callback) throws RemoteException {
-            callback.onResult(AppSearchResult.newSuccessfulResult(null));
+            callback.onResult(
+                    new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(null)));
         }
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 4bbea94..ddaf3ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1351,25 +1351,39 @@
      * must ensure the visibilities of activities being updated.
      */
     @Test
-    public void testCompleteFinishing_ensureActivitiesVisible() {
+    public void testCompleteFinishing_ensureActivitiesVisible_withConditions() {
+        testCompleteFinishing_ensureActivitiesVisible(false, PAUSED);
+        testCompleteFinishing_ensureActivitiesVisible(false, STARTED);
+        testCompleteFinishing_ensureActivitiesVisible(true, PAUSED);
+        testCompleteFinishing_ensureActivitiesVisible(true, STARTED);
+    }
+
+    private void testCompleteFinishing_ensureActivitiesVisible(boolean diffTask,
+            ActivityState secondActivityState) {
         final ActivityRecord activity = createActivityWithTask();
         final Task task = activity.getTask();
         final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
         firstActivity.mVisibleRequested = false;
         firstActivity.nowVisible = false;
-        firstActivity.setState(STOPPED, "true");
+        firstActivity.setState(STOPPED, "test");
 
         final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task).build();
         secondActivity.mVisibleRequested = true;
         secondActivity.nowVisible = true;
-        secondActivity.setState(PAUSED, "true");
+        secondActivity.setState(secondActivityState, "test");
 
-        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm).setTask(task).build();
+        ActivityRecord translucentActivity;
+        if (diffTask) {
+            translucentActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        } else {
+            translucentActivity = new ActivityBuilder(mAtm).setTask(task).build();
+        }
         translucentActivity.mVisibleRequested = true;
         translucentActivity.nowVisible = true;
-        translucentActivity.setState(RESUMED, "true");
+        translucentActivity.setState(RESUMED, "test");
 
-        doReturn(false).when(translucentActivity).occludesParent();
+        doReturn(true).when(firstActivity).occludesParent(true);
+        doReturn(true).when(secondActivity).occludesParent(true);
 
         // Finish the second activity
         secondActivity.finishing = true;
@@ -1385,6 +1399,10 @@
         verify(firstActivity.mDisplayContent, times(2)).ensureActivitiesVisible(null /* starting */,
                 0 /* configChanges */ , false /* preserveWindows */,
                 true /* notifyClients */);
+
+        // Remove the translucent activity and clear invocations for next test
+        translucentActivity.getTask().removeImmediately("test");
+        clearInvocations(mDefaultDisplay);
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 748622b..e280a36 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -38,6 +38,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+import static com.android.server.wm.Task.ActivityState.DESTROYED;
 import static com.android.server.wm.Task.ActivityState.DESTROYING;
 import static com.android.server.wm.Task.ActivityState.FINISHING;
 import static com.android.server.wm.Task.ActivityState.PAUSING;
@@ -1204,6 +1205,9 @@
         // See {@link ActivityStack#destroyActivityLocked}.
         activity.app = null;
         overlayActivity.app = null;
+        // Simulate the process is dead
+        activity.mVisibleRequested = false;
+        activity.setState(DESTROYED, "Test");
 
         assertEquals(2, task.getChildCount());
 
diff --git a/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java b/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java
index 0b47547..e9b7d95 100644
--- a/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java
+++ b/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.telephony;
+package com.android.internal.telephony;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 0d6cd5a..98db291 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -43,7 +43,6 @@
 import android.os.UserManager;
 import android.provider.Telephony;
 import android.provider.Telephony.Sms.Intents;
-import android.telephony.PackageChangeReceiver;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 import android.util.SparseArray;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 84ea42f..33a9a96 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3473,6 +3473,21 @@
             "additional_nr_advanced_bands_int_array";
 
     /**
+     * This configuration allows the framework to control the NR advanced capable by protocol
+     * configuration options(PCO).
+     *
+     * If this config is 0, then the nr advanced capable is enabled.
+     * If this config is not 0 and PCO container with this config's address is 1, then the nr
+     * advanced capable is enabled.
+     * If this config is not 0 and PCO container with this config's address is 0, then the nr
+     * advanced capable is disabled.
+     *
+     * @hide
+     */
+    public static final String KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT =
+            "nr_advanced_capable_pco_id_int";
+
+    /**
      * Controls time in milliseconds until DcTracker reevaluates 5G connection state.
      * @hide
      */
@@ -5436,6 +5451,7 @@
         /* Default value is 1 hour. */
         sDefaults.putLong(KEY_5G_WATCHDOG_TIME_MS_LONG, 3600000);
         sDefaults.putIntArray(KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY, new int[0]);
+        sDefaults.putInt(KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0);
         sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_BOOL, false);
         sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_MMWAVE_BOOL, false);
         sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_SUB6_BOOL, false);
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 9bd639b..b58aa11 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -32,7 +32,7 @@
 java_sdk_library {
     name: "android.test.base",
 
-    srcs: ["src/**/*.java"],
+    srcs: [":android-test-base-sources"],
 
     errorprone: {
         javacflags: ["-Xep:DepAnn:ERROR"],
@@ -66,7 +66,7 @@
     name: "android.test.base_static",
     installable: false,
 
-    srcs: ["src/**/*.java"],
+    srcs: [":android-test-base-sources"],
 
     errorprone: {
         javacflags: ["-Xep:DepAnn:ERROR"],
@@ -114,6 +114,12 @@
     ],
 }
 
+filegroup {
+    name: "android-test-base-sources",
+    srcs: ["src/**/*.java"],
+    path: "src",
+}
+
 // Make the current.txt available for use by the cts/tests/signature tests.
 // ========================================================================
 filegroup {
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index b83bce6..107292c 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -29,7 +29,7 @@
     name: "android.test.mock",
 
     srcs: [
-        "src/**/*.java",
+        ":android-test-mock-sources",
         // Note: Below are NOT APIs of this library. We only take APIs under
         // the android.test.mock package. They however provide private APIs that
         // android.test.mock APIs references to.
@@ -61,3 +61,9 @@
         "api/current.txt",
     ],
 }
+
+filegroup {
+    name: "android-test-mock-sources",
+    srcs: ["src/**/*.java"],
+    path: "src",
+}
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index fe007e39..c380ae3 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -29,7 +29,7 @@
 java_sdk_library {
     name: "android.test.runner",
 
-    srcs: ["src/**/*.java"],
+    srcs: [":android-test-runner-sources"],
 
     errorprone: {
         javacflags: ["-Xep:DepAnn:ERROR"],
@@ -76,7 +76,7 @@
 java_library_static {
     name: "repackaged.android.test.runner",
 
-    srcs: ["src/**/*.java"],
+    srcs: [":android-test-runner-sources"],
     exclude_srcs: [
         "src/android/test/ActivityUnitTestCase.java",
         "src/android/test/ApplicationTestCase.java",
@@ -108,3 +108,9 @@
         "api/current.txt",
     ],
 }
+
+filegroup {
+    name: "android-test-runner-sources",
+    srcs: ["src/**/*.java"],
+    path: "src",
+}
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 32c95f1..cf2c9c7 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -16,9 +16,14 @@
 
 package com.android.server;
 
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.INetd.IF_STATE_DOWN;
 import static android.net.INetd.IF_STATE_UP;
+import static android.net.IpSecManager.DIRECTION_FWD;
+import static android.net.IpSecManager.DIRECTION_IN;
+import static android.net.IpSecManager.DIRECTION_OUT;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_INET6;
 
@@ -56,6 +61,7 @@
 import android.os.ParcelFileDescriptor;
 import android.system.Os;
 import android.test.mock.MockContext;
+import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 
@@ -71,6 +77,7 @@
 import java.net.Socket;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Set;
 
 /** Unit tests for {@link IpSecService}. */
 @SmallTest
@@ -119,7 +126,18 @@
     AppOpsManager mMockAppOps = mock(AppOpsManager.class);
     ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class);
 
-    MockContext mMockContext = new MockContext() {
+    TestContext mTestContext = new TestContext();
+
+    private class TestContext extends MockContext {
+        private Set<String> mAllowedPermissions = new ArraySet<>(Arrays.asList(
+                android.Manifest.permission.MANAGE_IPSEC_TUNNELS,
+                android.Manifest.permission.NETWORK_STACK,
+                PERMISSION_MAINLINE_NETWORK_STACK));
+
+        private void setAllowedPermissions(String... permissions) {
+            mAllowedPermissions = new ArraySet<>(permissions);
+        }
+
         @Override
         public Object getSystemService(String name) {
             switch(name) {
@@ -147,20 +165,22 @@
 
         @Override
         public void enforceCallingOrSelfPermission(String permission, String message) {
-            if (permission == android.Manifest.permission.MANAGE_IPSEC_TUNNELS) {
+            if (mAllowedPermissions.contains(permission)) {
                 return;
+            } else {
+                throw new SecurityException("Unavailable permission requested");
             }
-            throw new SecurityException("Unavailable permission requested");
         }
 
         @Override
         public int checkCallingOrSelfPermission(String permission) {
-            if (android.Manifest.permission.NETWORK_STACK.equals(permission)) {
+            if (mAllowedPermissions.contains(permission)) {
                 return PERMISSION_GRANTED;
+            } else {
+                return PERMISSION_DENIED;
             }
-            throw new UnsupportedOperationException();
         }
-    };
+    }
 
     INetd mMockNetd;
     PackageManager mMockPkgMgr;
@@ -194,7 +214,7 @@
         mMockNetd = mock(INetd.class);
         mMockPkgMgr = mock(PackageManager.class);
         mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
-        mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
+        mIpSecService = new IpSecService(mTestContext, mMockIpSecSrvConfig);
 
         // Injecting mock netd
         when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd);
@@ -664,6 +684,21 @@
 
         assertNotNull(createTunnelResp);
         assertEquals(IpSecManager.Status.OK, createTunnelResp.status);
+        for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT, DIRECTION_FWD}) {
+            for (int selAddrFamily : ADDRESS_FAMILIES) {
+                verify(mMockNetd).ipSecAddSecurityPolicy(
+                        eq(mUid),
+                        eq(selAddrFamily),
+                        eq(direction),
+                        anyString(),
+                        anyString(),
+                        eq(0),
+                        anyInt(), // iKey/oKey
+                        anyInt(), // mask
+                        eq(createTunnelResp.resourceId));
+            }
+        }
+
         return createTunnelResp;
     }
 
@@ -798,16 +833,51 @@
     }
 
     @Test
-    public void testApplyTunnelModeTransform() throws Exception {
-        verifyApplyTunnelModeTransformCommon(false);
+    public void testApplyTunnelModeTransformOutbound() throws Exception {
+        verifyApplyTunnelModeTransformCommon(false /* closeSpiBeforeApply */, DIRECTION_OUT);
     }
 
     @Test
-    public void testApplyTunnelModeTransformReleasedSpi() throws Exception {
-        verifyApplyTunnelModeTransformCommon(true);
+    public void testApplyTunnelModeTransformOutboundNonNetworkStack() throws Exception {
+        mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS);
+        verifyApplyTunnelModeTransformCommon(false /* closeSpiBeforeApply */, DIRECTION_OUT);
     }
 
-    public void verifyApplyTunnelModeTransformCommon(boolean closeSpiBeforeApply) throws Exception {
+    @Test
+    public void testApplyTunnelModeTransformOutboundReleasedSpi() throws Exception {
+        verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_OUT);
+    }
+
+    @Test
+    public void testApplyTunnelModeTransformInbound() throws Exception {
+        verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_IN);
+    }
+
+    @Test
+    public void testApplyTunnelModeTransformInboundNonNetworkStack() throws Exception {
+        mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS);
+        verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_IN);
+    }
+
+    @Test
+    public void testApplyTunnelModeTransformForward() throws Exception {
+        verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_FWD);
+    }
+
+    @Test
+    public void testApplyTunnelModeTransformForwardNonNetworkStack() throws Exception {
+        mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS);
+
+        try {
+            verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_FWD);
+            fail("Expected security exception due to use of forward policies without NETWORK_STACK"
+                     + " or MAINLINE_NETWORK_STACK permission");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    public void verifyApplyTunnelModeTransformCommon(boolean closeSpiBeforeApply, int direction)
+            throws Exception {
         IpSecConfig ipSecConfig = new IpSecConfig();
         ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL);
         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
@@ -825,17 +895,17 @@
         int transformResourceId = createTransformResp.resourceId;
         int tunnelResourceId = createTunnelResp.resourceId;
         mIpSecService.applyTunnelModeTransform(
-                tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE);
+                tunnelResourceId, direction, transformResourceId, BLESSED_PACKAGE);
 
         for (int selAddrFamily : ADDRESS_FAMILIES) {
             verify(mMockNetd)
                     .ipSecUpdateSecurityPolicy(
                             eq(mUid),
                             eq(selAddrFamily),
-                            eq(IpSecManager.DIRECTION_OUT),
+                            eq(direction),
                             anyString(),
                             anyString(),
-                            eq(TEST_SPI),
+                            eq(direction == DIRECTION_OUT ? TEST_SPI : 0),
                             anyInt(), // iKey/oKey
                             anyInt(), // mask
                             eq(tunnelResourceId));
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index eedaac4..39f7386 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -16,8 +16,11 @@
 
 package com.android.server.vcn;
 
+import static android.net.IpSecManager.DIRECTION_FWD;
 import static android.net.IpSecManager.DIRECTION_IN;
 import static android.net.IpSecManager.DIRECTION_OUT;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
@@ -54,6 +57,8 @@
 import android.net.ipsec.ike.exceptions.IkeException;
 import android.net.ipsec.ike.exceptions.IkeInternalException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.net.vcn.VcnManager.VcnErrorCode;
 
 import androidx.test.filters.SmallTest;
@@ -143,8 +148,9 @@
         assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
     }
 
-    @Test
-    public void testCreatedTransformsAreApplied() throws Exception {
+    private void verifyVcnTransformsApplied(
+            VcnGatewayConnection vcnGatewayConnection, boolean expectForwardTransform)
+            throws Exception {
         for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) {
             getChildSessionCallback().onIpSecTransformCreated(makeDummyIpSecTransform(), direction);
             mTestLooper.dispatchAll();
@@ -154,7 +160,40 @@
                             eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
         }
 
-        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+        verify(mIpSecSvc, expectForwardTransform ? times(1) : never())
+                .applyTunnelModeTransform(
+                        eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(DIRECTION_FWD), anyInt(), any());
+
+        assertEquals(vcnGatewayConnection.mConnectedState, vcnGatewayConnection.getCurrentState());
+    }
+
+    @Test
+    public void testCreatedTransformsAreApplied() throws Exception {
+        verifyVcnTransformsApplied(mGatewayConnection, false /* expectForwardTransform */);
+    }
+
+    @Test
+    public void testCreatedTransformsAreAppliedWithDun() throws Exception {
+        VcnGatewayConnectionConfig gatewayConfig =
+                VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(
+                        NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN);
+        VcnGatewayConnection gatewayConnection =
+                new VcnGatewayConnection(
+                        mVcnContext,
+                        TEST_SUB_GRP,
+                        TEST_SUBSCRIPTION_SNAPSHOT,
+                        gatewayConfig,
+                        mGatewayStatusCallback,
+                        true /* isMobileDataEnabled */,
+                        mDeps);
+        gatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
+        final VcnIkeSession session =
+                gatewayConnection.buildIkeSession(TEST_UNDERLYING_NETWORK_RECORD_1.network);
+        gatewayConnection.setIkeSession(session);
+        gatewayConnection.transitionTo(gatewayConnection.mConnectedState);
+        mTestLooper.dispatchAll();
+
+        verifyVcnTransformsApplied(gatewayConnection, true /* expectForwardTransform */);
     }
 
     @Test
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 284f1f8..1ecb4c9 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -220,7 +220,7 @@
     protected VcnChildSessionCallback getChildSessionCallback() {
         ArgumentCaptor<ChildSessionCallback> captor =
                 ArgumentCaptor.forClass(ChildSessionCallback.class);
-        verify(mDeps).newIkeSession(any(), any(), any(), any(), captor.capture());
+        verify(mDeps, atLeastOnce()).newIkeSession(any(), any(), any(), any(), captor.capture());
         return (VcnChildSessionCallback) captor.getValue();
     }
 
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index f2c3b86..812e208 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -1121,8 +1121,8 @@
 
                 // Skip all "uses-sdk" tags besides the very last tag. The android runtime only uses
                 // the attribute values from the last defined tag.
-                for (size_t i = 0; i < usesSdkTagPositions.size() - 1; i++) {
-                    tagsToSkip.emplace_back(usesSdkTagPositions[i]);
+                for (size_t i = 1; i < usesSdkTagPositions.size(); i++) {
+                    tagsToSkip.emplace_back(usesSdkTagPositions[i - 1]);
                 }
 
                 // Reset the position before parsing.
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 77c0872..ef3a62f 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -278,17 +278,19 @@
       printer->Println(StringPrintf(" entryCount=%zd", type.entries.size()));
 
       printer->Indent();
-      for (const ResourceEntry* entry : type.entries) {
+      for (const ResourceTableEntryView& entry : type.entries) {
         printer->Print("resource ");
-        printer->Print(entry->id.value_or_default(0).to_string());
+        printer->Print(ResourceId(package.id.value_or_default(0), type.id.value_or_default(0),
+                                  entry.id.value_or_default(0))
+                           .to_string());
         printer->Print(" ");
 
         // Write the name without the package (this is obvious and too verbose).
         printer->Print(to_string(type.type));
         printer->Print("/");
-        printer->Print(entry->name);
+        printer->Print(entry.name);
 
-        switch (entry->visibility.level) {
+        switch (entry.visibility.level) {
           case Visibility::Level::kPublic:
             printer->Print(" PUBLIC");
             break;
@@ -300,19 +302,24 @@
             break;
         }
 
-        if (entry->visibility.staged_api) {
+        if (entry.visibility.staged_api) {
           printer->Print(" STAGED");
         }
 
-        if (entry->overlayable_item) {
+        if (entry.overlayable_item) {
           printer->Print(" OVERLAYABLE");
         }
 
+        if (entry.staged_id) {
+          printer->Print(" STAGED_ID=");
+          printer->Print(entry.staged_id.value().id.to_string());
+        }
+
         printer->Println();
 
         if (options.show_values) {
           printer->Indent();
-          for (const auto& value : entry->values) {
+          for (const auto& value : entry.values) {
             printer->Print("(");
             printer->Print(value->config.to_string());
             printer->Print(") ");
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 1efabbb..f1e2da9 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -45,6 +45,7 @@
 namespace {
 constexpr const char* kPublicGroupTag = "public-group";
 constexpr const char* kStagingPublicGroupTag = "staging-public-group";
+constexpr const char* kStagingPublicGroupFinalTag = "staging-public-group-final";
 }  // namespace
 
 constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
@@ -109,6 +110,7 @@
   bool staged_api = false;
   bool allow_new = false;
   Maybe<OverlayableItem> overlayable_item;
+  Maybe<StagedId> staged_alias;
 
   std::string comment;
   std::unique_ptr<Value> value;
@@ -155,6 +157,10 @@
     res_builder.SetValue(std::move(res->value), res->config, res->product);
   }
 
+  if (res->staged_alias) {
+    res_builder.SetStagedId(res->staged_alias.value());
+  }
+
   bool error = false;
   if (!res->name.entry.empty()) {
     if (!table->AddResource(res_builder.Build(), diag)) {
@@ -532,6 +538,7 @@
       {"public", std::mem_fn(&ResourceParser::ParsePublic)},
       {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)},
       {"staging-public-group", std::mem_fn(&ResourceParser::ParseStagingPublicGroup)},
+      {"staging-public-group-final", std::mem_fn(&ResourceParser::ParseStagingPublicGroupFinal)},
       {"string-array", std::mem_fn(&ResourceParser::ParseStringArray)},
       {"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle,
                           std::placeholders::_2, std::placeholders::_3)},
@@ -671,7 +678,7 @@
     if (bag_iter != elToBagMap.end()) {
       // Ensure we have a name (unless this is a <public-group> or <overlayable>).
       if (resource_type != kPublicGroupTag && resource_type != kStagingPublicGroupTag &&
-          resource_type != "overlayable") {
+          resource_type != kStagingPublicGroupFinalTag && resource_type != "overlayable") {
         if (!maybe_name) {
           diag_->Error(DiagMessage(out_resource->source)
                        << "<" << parser->element_name() << "> missing 'name' attribute");
@@ -1034,7 +1041,6 @@
       ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{
           .name = ResourceName{{}, *parsed_type, maybe_name.value().to_string()},
           .source = item_source,
-          .id = next_id,
           .comment = std::move(comment),
       });
 
@@ -1060,6 +1066,14 @@
                         });
 }
 
+bool ResourceParser::ParseStagingPublicGroupFinal(xml::XmlPullParser* parser,
+                                                  ParsedResource* out_resource) {
+  return ParseGroupImpl(parser, out_resource, kStagingPublicGroupFinalTag, diag_,
+                        [](ParsedResource& parsed_entry, ResourceId id) {
+                          parsed_entry.staged_alias = StagedId{id, parsed_entry.source};
+                        });
+}
+
 bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) {
   if (options_.visibility) {
     diag_->Error(DiagMessage(out_resource->source)
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 5c92def..2614997 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -115,6 +115,7 @@
   bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
+  bool ParseStagingPublicGroupFinal(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource);
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 45ea654..8ab1493 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -83,6 +83,20 @@
   return action(found, iter);
 }
 
+struct ConfigKey {
+  const ConfigDescription* config;
+  const StringPiece& product;
+};
+
+template <typename T>
+bool lt_config_key_ref(const T& lhs, const ConfigKey& rhs) {
+  int cmp = lhs->config.compare(*rhs.config);
+  if (cmp == 0) {
+    cmp = StringPiece(lhs->product).compare(rhs.product);
+  }
+  return cmp < 0;
+}
+
 }  // namespace
 
 ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) {
@@ -134,23 +148,10 @@
   });
 }
 
-struct ConfigKey {
-  const ConfigDescription* config;
-  const StringPiece& product;
-};
-
-bool lt_config_key_ref(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) {
-  int cmp = lhs->config.compare(*rhs.config);
-  if (cmp == 0) {
-    cmp = StringPiece(lhs->product).compare(rhs.product);
-  }
-  return cmp < 0;
-}
-
 ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config,
                                               android::StringPiece product) {
   auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
-                               lt_config_key_ref);
+                               lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
   if (iter != values.end()) {
     ResourceConfigValue* value = iter->get();
     if (value->config == config && StringPiece(value->product) == product) {
@@ -163,7 +164,7 @@
 const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescription& config,
                                                     android::StringPiece product) const {
   auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
-                               lt_config_key_ref);
+                               lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
   if (iter != values.end()) {
     ResourceConfigValue* value = iter->get();
     if (value->config == config && StringPiece(value->product) == product) {
@@ -176,7 +177,7 @@
 ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config,
                                                       const StringPiece& product) {
   auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
-                               lt_config_key_ref);
+                               lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
   if (iter != values.end()) {
     ResourceConfigValue* value = iter->get();
     if (value->config == config && StringPiece(value->product) == product) {
@@ -296,6 +297,7 @@
   return CollisionResult::kConflict;
 }
 
+namespace {
 template <typename T, typename Comparer>
 struct SortedVectorInserter : public Comparer {
   std::pair<bool, typename std::vector<T>::iterator> LowerBound(std::vector<T>& el,
@@ -313,7 +315,7 @@
     if (found) {
       return &*it;
     }
-    return &*el.insert(it, std::move(value));
+    return &*el.insert(it, std::forward<T>(value));
   }
 };
 
@@ -331,35 +333,77 @@
 };
 
 struct EntryViewComparer {
-  bool operator()(const ResourceEntry* lhs, const ResourceEntry* rhs) {
-    return less_than_struct_with_name_and_id<ResourceEntry, ResourceId>(
-        *lhs, std::make_pair(rhs->name, rhs->id));
+  bool operator()(const ResourceTableEntryView& lhs, const ResourceTableEntryView& rhs) {
+    return less_than_struct_with_name_and_id<ResourceTableEntryView, uint16_t>(
+        lhs, std::make_pair(rhs.name, rhs.id));
   }
 };
 
-ResourceTableView ResourceTable::GetPartitionedView() const {
-  ResourceTableView view;
+void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePackage* package,
+                              const ResourceTableType* type, const std::string& entry_name,
+                              const Maybe<ResourceId>& id, const Visibility& visibility,
+                              const Maybe<AllowNew>& allow_new,
+                              const Maybe<OverlayableItem>& overlayable_item,
+                              const Maybe<StagedId>& staged_id,
+                              const std::vector<std::unique_ptr<ResourceConfigValue>>& values) {
   SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
   SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
-  SortedVectorInserter<const ResourceEntry*, EntryViewComparer> entry_inserter;
+  SortedVectorInserter<ResourceTableEntryView, EntryViewComparer> entry_inserter;
 
+  ResourceTablePackageView new_package{package->name,
+                                       id ? id.value().package_id() : Maybe<uint8_t>{}};
+  auto view_package = package_inserter.Insert(table.packages, std::move(new_package));
+
+  ResourceTableTypeView new_type{type->type, id ? id.value().type_id() : Maybe<uint8_t>{}};
+  auto view_type = type_inserter.Insert(view_package->types, std::move(new_type));
+
+  if (visibility.level == Visibility::Level::kPublic) {
+    // Only mark the type visibility level as public, it doesn't care about being private.
+    view_type->visibility_level = Visibility::Level::kPublic;
+  }
+
+  ResourceTableEntryView new_entry{.name = entry_name,
+                                   .id = id ? id.value().entry_id() : Maybe<uint16_t>{},
+                                   .visibility = visibility,
+                                   .allow_new = allow_new,
+                                   .overlayable_item = overlayable_item,
+                                   .staged_id = staged_id};
+  for (auto& value : values) {
+    new_entry.values.emplace_back(value.get());
+  }
+
+  entry_inserter.Insert(view_type->entries, std::move(new_entry));
+}
+}  // namespace
+
+const ResourceConfigValue* ResourceTableEntryView::FindValue(const ConfigDescription& config,
+                                                             android::StringPiece product) const {
+  auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
+                               lt_config_key_ref<const ResourceConfigValue*>);
+  if (iter != values.end()) {
+    const ResourceConfigValue* value = *iter;
+    if (value->config == config && StringPiece(value->product) == product) {
+      return value;
+    }
+  }
+  return nullptr;
+}
+
+ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptions& options) const {
+  ResourceTableView view;
   for (const auto& package : packages) {
     for (const auto& type : package->types) {
       for (const auto& entry : type->entries) {
-        ResourceTablePackageView new_package{
-            package->name, entry->id ? entry->id.value().package_id() : Maybe<uint8_t>{}};
-        auto view_package = package_inserter.Insert(view.packages, std::move(new_package));
+        InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, entry->id,
+                                 entry->visibility, entry->allow_new, entry->overlayable_item,
+                                 entry->staged_id, entry->values);
 
-        ResourceTableTypeView new_type{type->type,
-                                       entry->id ? entry->id.value().type_id() : Maybe<uint8_t>{}};
-        auto view_type = type_inserter.Insert(view_package->types, std::move(new_type));
-
-        if (entry->visibility.level == Visibility::Level::kPublic) {
-          // Only mark the type visibility level as public, it doesn't care about being private.
-          view_type->visibility_level = Visibility::Level::kPublic;
+        if (options.create_alias_entries && entry->staged_id) {
+          auto alias_id = entry->staged_id.value().id;
+          InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, alias_id,
+                                   entry->visibility, entry->allow_new, entry->overlayable_item, {},
+                                   entry->values);
         }
-
-        entry_inserter.Insert(view_type->entries, entry.get());
       }
     }
   }
@@ -368,6 +412,8 @@
   // for the same resource type within the same package. For this reason, if there are types with
   // multiple type ids, each type needs to exist in its own package in order to be queried by name.
   std::vector<ResourceTablePackageView> new_packages;
+  SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
+  SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
   for (auto& package : view.packages) {
     // If a new package was already created for a different type within this package, then
     // we can reuse those packages for other types that need to be extracted from this package.
@@ -498,6 +544,10 @@
     entry->allow_new = res.allow_new.value();
   }
 
+  if (res.staged_id.has_value()) {
+    entry->staged_id = res.staged_id.value();
+  }
+
   if (res.value != nullptr) {
     auto config_value = entry->FindOrCreateValue(res.config, res.product);
     if (!config_value->value) {
@@ -575,6 +625,28 @@
   return {};
 }
 
+bool ResourceTable::RemoveResource(const ResourceNameRef& name, ResourceId id) const {
+  ResourceTablePackage* package = FindPackage(name.package);
+  if (package == nullptr) {
+    return {};
+  }
+
+  ResourceTableType* type = package->FindType(name.type);
+  if (type == nullptr) {
+    return {};
+  }
+
+  auto entry_it = std::equal_range(type->entries.begin(), type->entries.end(), name.entry,
+                                   NameEqualRange<ResourceEntry>{});
+  for (auto it = entry_it.first; it != entry_it.second; ++it) {
+    if ((*it)->id == id) {
+      type->entries.erase(it);
+      return true;
+    }
+  }
+  return false;
+}
+
 std::unique_ptr<ResourceTable> ResourceTable::Clone() const {
   std::unique_ptr<ResourceTable> new_table = util::make_unique<ResourceTable>();
   CloningValueTransformer cloner(&new_table->string_pool);
@@ -640,6 +712,11 @@
   return *this;
 }
 
+NewResourceBuilder& NewResourceBuilder::SetStagedId(StagedId staged_alias) {
+  res_.staged_id = std::move(staged_alias);
+  return *this;
+}
+
 NewResourceBuilder& NewResourceBuilder::SetAllowMangled(bool allow_mangled) {
   res_.allow_mangled = allow_mangled;
   return *this;
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 080ecc2..bae1d82 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -64,6 +64,12 @@
   std::string comment;
 };
 
+// Represents the staged resource id of a finalized resource.
+struct StagedId {
+  ResourceId id;
+  Source source;
+};
+
 struct Overlayable {
   Overlayable() = default;
    Overlayable(const android::StringPiece& name, const android::StringPiece& actor)
@@ -124,6 +130,9 @@
   // The declarations of this resource as overlayable for RROs
   Maybe<OverlayableItem> overlayable_item;
 
+  // The staged resource id for a finalized resource.
+  Maybe<StagedId> staged_id;
+
   // The resource's values for each configuration.
   std::vector<std::unique_ptr<ResourceConfigValue>> values;
 
@@ -194,14 +203,27 @@
   DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
 };
 
+struct ResourceTableEntryView {
+  std::string name;
+  Maybe<uint16_t> id;
+  Visibility visibility;
+  Maybe<AllowNew> allow_new;
+  Maybe<OverlayableItem> overlayable_item;
+  Maybe<StagedId> staged_id;
+  std::vector<const ResourceConfigValue*> values;
+
+  const ResourceConfigValue* FindValue(const android::ConfigDescription& config,
+                                       android::StringPiece product = {}) const;
+};
+
 struct ResourceTableTypeView {
   ResourceType type;
   Maybe<uint8_t> id;
   Visibility::Level visibility_level = Visibility::Level::kUndefined;
 
   // Entries sorted in ascending entry id order. If ids have not been assigned, the entries are
-  //  // sorted lexicographically.
-  std::vector<const ResourceEntry*> entries;
+  // sorted lexicographically.
+  std::vector<ResourceTableEntryView> entries;
 };
 
 struct ResourceTablePackageView {
@@ -212,6 +234,10 @@
   std::vector<ResourceTableTypeView> types;
 };
 
+struct ResourceTableViewOptions {
+  bool create_alias_entries = false;
+};
+
 struct ResourceTableView {
   // Packages sorted in ascending package id order. If ids have not been assigned, the packages are
   // sorted lexicographically.
@@ -237,6 +263,7 @@
   std::optional<Visibility> visibility;
   std::optional<OverlayableItem> overlayable;
   std::optional<AllowNew> allow_new;
+  std::optional<StagedId> staged_id;
   bool allow_mangled = false;
 };
 
@@ -249,6 +276,7 @@
   NewResourceBuilder& SetVisibility(Visibility id);
   NewResourceBuilder& SetOverlayable(OverlayableItem overlayable);
   NewResourceBuilder& SetAllowNew(AllowNew allow_new);
+  NewResourceBuilder& SetStagedId(StagedId id);
   NewResourceBuilder& SetAllowMangled(bool allow_mangled);
   NewResource Build();
 
@@ -273,7 +301,7 @@
 
   // Retrieves a sorted a view of the packages, types, and entries sorted in ascending resource id
   // order.
-  ResourceTableView GetPartitionedView() const;
+  ResourceTableView GetPartitionedView(const ResourceTableViewOptions& options = {}) const;
 
   struct SearchResult {
     ResourceTablePackage* package;
@@ -283,6 +311,7 @@
 
   Maybe<SearchResult> FindResource(const ResourceNameRef& name) const;
   Maybe<SearchResult> FindResource(const ResourceNameRef& name, ResourceId id) const;
+  bool RemoveResource(const ResourceNameRef& name, ResourceId id) const;
 
   // Returns the package struct with the given name, or nullptr if such a package does not
   // exist. The empty string is a valid package and typically is used to represent the
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index b45c040..95b7949 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -190,6 +190,12 @@
   uint32 overlayable_idx = 4;
 }
 
+// The staged resource ID definition of a finalized resource.
+message StagedId {
+  Source source = 1;
+  uint32 staged_id = 2;
+}
+
 // An entry ID in the range [0x0000, 0xffff].
 message EntryId {
   uint32 id = 1;
@@ -222,6 +228,9 @@
   // The set of values defined for this entry, each corresponding to a different
   // configuration/variant.
   repeated ConfigValue config_value = 6;
+
+  // The staged resource ID of this finalized resource.
+  StagedId staged_id = 7;
 }
 
 // A Configuration/Value pair.
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index df31087..3950f33 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -97,15 +97,15 @@
 
 static bool EmitResourceConfigValueDiff(
     IAaptContext* context, LoadedApk* apk_a, const ResourceTablePackageView& pkg_a,
-    const ResourceTableTypeView& type_a, const ResourceEntry* entry_a,
+    const ResourceTableTypeView& type_a, const ResourceTableEntryView& entry_a,
     const ResourceConfigValue* config_value_a, LoadedApk* apk_b,
     const ResourceTablePackageView& pkg_b, const ResourceTableTypeView& type_b,
-    const ResourceEntry* entry_b, const ResourceConfigValue* config_value_b) {
+    const ResourceTableEntryView& entry_b, const ResourceConfigValue* config_value_b) {
   Value* value_a = config_value_a->value.get();
   Value* value_b = config_value_b->value.get();
   if (!value_a->Equals(value_b)) {
     std::stringstream str_stream;
-    str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
+    str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
                << " config=" << config_value_a->config << " does not match:\n";
     value_a->Print(&str_stream);
     str_stream << "\n vs \n";
@@ -118,32 +118,32 @@
 
 static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
                                   const ResourceTablePackageView& pkg_a,
-                                  const ResourceTableTypeView& type_a, const ResourceEntry* entry_a,
-                                  LoadedApk* apk_b, const ResourceTablePackageView& pkg_b,
+                                  const ResourceTableTypeView& type_a,
+                                  const ResourceTableEntryView& entry_a, LoadedApk* apk_b,
+                                  const ResourceTablePackageView& pkg_b,
                                   const ResourceTableTypeView& type_b,
-                                  const ResourceEntry* entry_b) {
+                                  const ResourceTableEntryView& entry_b) {
   bool diff = false;
-  for (const std::unique_ptr<ResourceConfigValue>& config_value_a : entry_a->values) {
-    auto config_value_b = entry_b->FindValue(config_value_a->config);
+  for (const ResourceConfigValue* config_value_a : entry_a.values) {
+    auto config_value_b = entry_b.FindValue(config_value_a->config);
     if (!config_value_b) {
       std::stringstream str_stream;
-      str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
+      str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
                  << " config=" << config_value_a->config;
       EmitDiffLine(apk_b->GetSource(), str_stream.str());
       diff = true;
     } else {
-      diff |=
-          EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(),
-                                      apk_b, pkg_b, type_b, entry_b, config_value_b);
+      diff |= EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a,
+                                          apk_b, pkg_b, type_b, entry_b, config_value_b);
     }
   }
 
   // Check for any newly added config values.
-  for (const std::unique_ptr<ResourceConfigValue>& config_value_b : entry_b->values) {
-    auto config_value_a = entry_a->FindValue(config_value_b->config);
+  for (const ResourceConfigValue* config_value_b : entry_b.values) {
+    auto config_value_a = entry_a.FindValue(config_value_b->config);
     if (!config_value_a) {
       std::stringstream str_stream;
-      str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b->name
+      str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b.name
                  << " config=" << config_value_b->config;
       EmitDiffLine(apk_b->GetSource(), str_stream.str());
       diff = true;
@@ -164,36 +164,35 @@
     if (entry_b_iter == type_b.entries.end()) {
       // Type A contains a type that type B does not have.
       std::stringstream str_stream;
-      str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << (*entry_a_iter)->name;
+      str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a_iter->name;
       EmitDiffLine(apk_a->GetSource(), str_stream.str());
       diff = true;
     } else if (entry_a_iter == type_a.entries.end()) {
       // Type B contains a type that type A does not have.
       std::stringstream str_stream;
-      str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/"
-                 << (*entry_b_iter)->name;
+      str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/" << entry_b_iter->name;
       EmitDiffLine(apk_b->GetSource(), str_stream.str());
       diff = true;
     } else {
       const auto& entry_a = *entry_a_iter;
       const auto& entry_b = *entry_b_iter;
-      if (IsSymbolVisibilityDifferent(entry_a->visibility, entry_b->visibility)) {
+      if (IsSymbolVisibilityDifferent(entry_a.visibility, entry_b.visibility)) {
         std::stringstream str_stream;
-        str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
+        str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
                    << " has different visibility (";
-        if (entry_b->visibility.staged_api) {
+        if (entry_b.visibility.staged_api) {
           str_stream << "STAGED ";
         }
-        if (entry_b->visibility.level == Visibility::Level::kPublic) {
+        if (entry_b.visibility.level == Visibility::Level::kPublic) {
           str_stream << "PUBLIC";
         } else {
           str_stream << "PRIVATE";
         }
         str_stream << " vs ";
-        if (entry_a->visibility.staged_api) {
+        if (entry_a.visibility.staged_api) {
           str_stream << "STAGED ";
         }
-        if (entry_a->visibility.level == Visibility::Level::kPublic) {
+        if (entry_a.visibility.level == Visibility::Level::kPublic) {
           str_stream << "PUBLIC";
         } else {
           str_stream << "PRIVATE";
@@ -201,19 +200,19 @@
         str_stream << ")";
         EmitDiffLine(apk_b->GetSource(), str_stream.str());
         diff = true;
-      } else if (IsIdDiff(entry_a->visibility.level, entry_a->id, entry_b->visibility.level,
-                          entry_b->id)) {
+      } else if (IsIdDiff(entry_a.visibility.level, entry_a.id, entry_b.visibility.level,
+                          entry_b.id)) {
         std::stringstream str_stream;
-        str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
+        str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
                    << " has different public ID (";
-        if (entry_b->id) {
-          str_stream << "0x" << std::hex << entry_b->id.value();
+        if (entry_b.id) {
+          str_stream << "0x" << std::hex << entry_b.id.value();
         } else {
           str_stream << "none";
         }
         str_stream << " vs ";
-        if (entry_a->id) {
-          str_stream << "0x " << std::hex << entry_a->id.value();
+        if (entry_a.id) {
+          str_stream << "0x " << std::hex << entry_a.id.value();
         } else {
           str_stream << "none";
         }
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 3118eb8..430c184 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -402,8 +402,39 @@
   EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000"));
 }
 
-TEST_F(LinkTest, StagedAndroidApi) {
-  StdErrDiagnostics diag;
+struct SourceXML {
+  std::string res_file_path;
+  std::string file_contents;
+};
+
+static void BuildApk(const std::vector<SourceXML>& source_files, const std::string& apk_path,
+                     LinkCommandBuilder&& link_args, CommandTestFixture* fixture,
+                     IDiagnostics* diag) {
+  TemporaryDir res_dir;
+  TemporaryDir compiled_res_dir;
+  for (auto& source_file : source_files) {
+    ASSERT_TRUE(fixture->CompileFile(res_dir.path + source_file.res_file_path,
+                                     source_file.file_contents, compiled_res_dir.path, diag));
+  }
+  ASSERT_TRUE(fixture->Link(
+      link_args.AddCompiledResDir(compiled_res_dir.path, diag).Build(apk_path), diag));
+}
+
+static void BuildSDK(const std::vector<SourceXML>& source_files, const std::string& apk_path,
+                     const std::string& java_root_path, CommandTestFixture* fixture,
+                     IDiagnostics* diag) {
+  auto android_manifest = ManifestBuilder(fixture).SetPackageName("android").Build();
+
+  auto android_link_args = LinkCommandBuilder(fixture)
+                               .SetManifestFile(android_manifest)
+                               .AddParameter("--private-symbols", "com.android.internal")
+                               .AddParameter("--java", java_root_path);
+
+  BuildApk(source_files, apk_path, std::move(android_link_args), fixture, diag);
+}
+
+static void BuildNonFinalizedSDK(const std::string& apk_path, const std::string& java_path,
+                                 CommandTestFixture* fixture, IDiagnostics* diag) {
   const std::string android_values =
       R"(<resources>
           <public type="attr" name="finalized_res" id="0x01010001"/>
@@ -413,6 +444,10 @@
             <public name="staged_s_res" />
           </staging-public-group>
 
+          <staging-public-group type="string" first-id="0x01fd0080">
+            <public name="staged_s_string" />
+          </staging-public-group>
+
           <!-- SV2 staged attributes (support staged resources in a separate type id) -->
           <staging-public-group type="attr" first-id="0x01ff0049">
             <public name="staged_s2_res" />
@@ -423,46 +458,90 @@
             <public name="staged_t_res" />
           </staging-public-group>
 
-          <staging-public-group type="string" first-id="0x01fd0072">
-            <public name="staged_t_string" />
+          <attr name="finalized_res" />
+          <attr name="staged_s_res" />
+          <attr name="staged_s2_res" />
+          <attr name="staged_t_res" />
+          <string name="staged_s_string">Hello</string>
+         </resources>)";
+
+  SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
+  BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
+}
+
+static void BuildFinalizedSDK(const std::string& apk_path, const std::string& java_path,
+                              CommandTestFixture* fixture, IDiagnostics* diag) {
+  const std::string android_values =
+      R"(<resources>
+          <public type="attr" name="finalized_res" id="0x01010001"/>
+          <public type="attr" name="staged_s_res" id="0x01010002"/>
+          <public type="attr" name="staged_s2_res" id="0x01010003"/>
+          <public type="string" name="staged_s_string" id="0x01020000"/>
+
+          <!-- S staged attributes (support staged resources in the same type id) -->
+          <staging-public-group-final type="attr" first-id="0x01010050">
+            <public name="staged_s_res" />
+          </staging-public-group-final>
+
+          <staging-public-group-final type="string" first-id="0x01fd0080">
+            <public name="staged_s_string" />
+          </staging-public-group-final>
+
+          <!-- SV2 staged attributes (support staged resources in a separate type id) -->
+          <staging-public-group-final type="attr" first-id="0x01ff0049">
+            <public name="staged_s2_res" />
+          </staging-public-group-final>
+
+          <!-- T staged attributes (support staged resources in multiple separate type ids) -->
+          <staging-public-group type="attr" first-id="0x01fe0063">
+            <public name="staged_t_res" />
           </staging-public-group>
 
           <attr name="finalized_res" />
           <attr name="staged_s_res" />
           <attr name="staged_s2_res" />
           <attr name="staged_t_res" />
-          <string name="staged_t_string">Hello</string>
+          <string name="staged_s_string">Hello</string>
          </resources>)";
 
+  SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
+  BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
+}
+
+static void BuildAppAgainstSDK(const std::string& apk_path, const std::string& java_path,
+                               const std::string& sdk_path, CommandTestFixture* fixture,
+                               IDiagnostics* diag) {
   const std::string app_values =
       R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
            <attr name="bar" />
+           <style name="MyStyle">
+             <item name="android:staged_s_res">@android:string/staged_s_string</item>
+           </style>
            <declare-styleable name="ClientStyleable">
              <attr name="android:finalized_res" />
              <attr name="android:staged_s_res" />
              <attr name="bar" />
            </declare-styleable>
+           <public name="MyStyle" type="style" id="0x7f020000" />
          </resources>)";
 
-  const std::string android_res = GetTestPath("android-res");
-  ASSERT_TRUE(
-      CompileFile(GetTestPath("res/values/values.xml"), android_values, android_res, &diag));
+  SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = app_values};
 
+  auto app_manifest = ManifestBuilder(fixture).SetPackageName("com.example.app").Build();
+
+  auto app_link_args = LinkCommandBuilder(fixture)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("--java", java_path)
+                           .AddParameter("-I", sdk_path);
+
+  BuildApk({source_xml}, apk_path, std::move(app_link_args), fixture, diag);
+}
+
+TEST_F(LinkTest, StagedAndroidApi) {
+  StdErrDiagnostics diag;
   const std::string android_apk = GetTestPath("android.apk");
-  const std::string android_java = GetTestPath("android_java");
-  // clang-format off
-  auto android_manifest = ManifestBuilder(this)
-      .SetPackageName("android")
-      .Build();
-
-  auto android_link_args = LinkCommandBuilder(this)
-      .SetManifestFile(android_manifest)
-      .AddParameter("--private-symbols", "com.android.internal")
-      .AddParameter("--java", android_java)
-      .AddCompiledResDir(android_res, &diag)
-      .Build(android_apk);
-  // clang-format on
-  ASSERT_TRUE(Link(android_link_args, &diag));
+  const std::string android_java = GetTestPath("android-java");
+  BuildNonFinalizedSDK(android_apk, android_java, this, &diag);
 
   const std::string android_r_java = android_java + "/android/R.java";
   std::string android_r_contents;
@@ -473,33 +552,17 @@
       HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01010050; }"));
   EXPECT_THAT(
       android_r_contents,
+      HasSubstr("public static final int staged_s_string; static { staged_s_string=0x01fd0080; }"));
+  EXPECT_THAT(
+      android_r_contents,
       HasSubstr("public static final int staged_s2_res; static { staged_s2_res=0x01ff0049; }"));
   EXPECT_THAT(
       android_r_contents,
       HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
-  EXPECT_THAT(
-      android_r_contents,
-      HasSubstr("public static final int staged_t_string; static { staged_t_string=0x01fd0072; }"));
-
-  // Build an app that uses the framework attribute in a declare-styleable
-  const std::string client_res = GetTestPath("app-res");
-  ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), app_values, client_res, &diag));
 
   const std::string app_apk = GetTestPath("app.apk");
-  const std::string app_java = GetTestPath("app_java");
-  // clang-format off
-  auto app_manifest = ManifestBuilder(this)
-      .SetPackageName("com.example.app")
-      .Build();
-
-  auto app_link_args = LinkCommandBuilder(this)
-      .SetManifestFile(app_manifest)
-      .AddParameter("--java", app_java)
-      .AddParameter("-I", android_apk)
-      .AddCompiledResDir(client_res, &diag)
-      .Build(app_apk);
-  // clang-format on
-  ASSERT_TRUE(Link(app_link_args, &diag));
+  const std::string app_java = GetTestPath("app-java");
+  BuildAppAgainstSDK(app_apk, app_java, android_apk, this, &diag);
 
   const std::string client_r_java = app_java + "/com/example/app/R.java";
   std::string client_r_contents;
@@ -520,6 +583,10 @@
   ASSERT_TRUE(result.has_value());
   EXPECT_THAT(*result, Eq(0x01010050));
 
+  result = am.GetResourceId("android:string/staged_s_string");
+  ASSERT_TRUE(result.has_value());
+  EXPECT_THAT(*result, Eq(0x01fd0080));
+
   result = am.GetResourceId("android:attr/staged_s2_res");
   ASSERT_TRUE(result.has_value());
   EXPECT_THAT(*result, Eq(0x01ff0049));
@@ -527,10 +594,88 @@
   result = am.GetResourceId("android:attr/staged_t_res");
   ASSERT_TRUE(result.has_value());
   EXPECT_THAT(*result, Eq(0x01fe0063));
+}
 
-  result = am.GetResourceId("android:string/staged_t_string");
+TEST_F(LinkTest, FinalizedAndroidApi) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildFinalizedSDK(android_apk, android_java, this, &diag);
+
+  const std::string android_r_java = android_java + "/android/R.java";
+  std::string android_r_contents;
+  ASSERT_TRUE(android::base::ReadFileToString(android_r_java, &android_r_contents));
+  EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;"));
+  EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_res=0x01010002;"));
+  EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_string=0x01020000;"));
+  EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s2_res=0x01010003;"));
+  EXPECT_THAT(
+      android_r_contents,
+      HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
+  ;
+
+  // Build an application against the non-finalized SDK and then load it into an AssetManager with
+  // the finalized SDK.
+  const std::string non_finalized_android_apk = GetTestPath("non-finalized-android.apk");
+  const std::string non_finalized_android_java = GetTestPath("non-finalized-android-java");
+  BuildNonFinalizedSDK(non_finalized_android_apk, non_finalized_android_java, this, &diag);
+
+  const std::string app_apk = GetTestPath("app.apk");
+  const std::string app_java = GetTestPath("app-java");
+  BuildAppAgainstSDK(app_apk, app_java, non_finalized_android_apk, this, &diag);
+
+  android::AssetManager2 am;
+  auto android_asset = android::ApkAssets::Load(android_apk);
+  auto app_against_non_final = android::ApkAssets::Load(app_apk);
+  ASSERT_THAT(android_asset, NotNull());
+  ASSERT_THAT(app_against_non_final, NotNull());
+  ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_non_final.get()}));
+
+  auto result = am.GetResourceId("android:attr/finalized_res");
   ASSERT_TRUE(result.has_value());
-  EXPECT_THAT(*result, Eq(0x01fd0072));
+  EXPECT_THAT(*result, Eq(0x01010001));
+
+  result = am.GetResourceId("android:attr/staged_s_res");
+  ASSERT_TRUE(result.has_value());
+  EXPECT_THAT(*result, Eq(0x01010002));
+
+  result = am.GetResourceId("android:string/staged_s_string");
+  ASSERT_TRUE(result.has_value());
+  EXPECT_THAT(*result, Eq(0x01020000));
+
+  result = am.GetResourceId("android:attr/staged_s2_res");
+  ASSERT_TRUE(result.has_value());
+  EXPECT_THAT(*result, Eq(0x01010003));
+
+  {
+    auto style = am.GetBag(0x7f020000);
+    ASSERT_TRUE(style.has_value());
+
+    auto& entry = (*style)->entries[0];
+    EXPECT_THAT(entry.key, Eq(0x01010002));
+    EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
+    EXPECT_THAT(entry.value.data, Eq(0x01020000));
+  }
+
+  // Re-compile the application against the finalized SDK and then load it into an AssetManager with
+  // the finalized SDK.
+  const std::string app_apk_respin = GetTestPath("app-respin.apk");
+  const std::string app_java_respin = GetTestPath("app-respin-java");
+  BuildAppAgainstSDK(app_apk_respin, app_java_respin, android_apk, this, &diag);
+
+  auto app_against_final = android::ApkAssets::Load(app_apk_respin);
+  ASSERT_THAT(app_against_final, NotNull());
+  ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_final.get()}));
+
+  {
+    auto style = am.GetBag(0x7f020000);
+    ASSERT_TRUE(style.has_value());
+
+    auto& entry = (*style)->entries[0];
+    EXPECT_THAT(entry.key, Eq(0x01010002));
+    EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
+    EXPECT_THAT(entry.value.data, Eq(0x01020000));
+  }
 }
 
 TEST_F(LinkTest, MacroSubstitution) {
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
index 9a50b26..339b8af 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -129,11 +129,16 @@
     for (auto& type : package->types) {
       for (auto& entry : type->entries) {
         const ResourceName name(package->name, type->type, entry->name);
-        if (entry->id) {
-          if (!assigned_ids.ReserveId(name, entry->id.value(), entry->visibility,
-                                      context->GetDiagnostics())) {
-            return false;
-          }
+        if (entry->id && !assigned_ids.ReserveId(name, entry->id.value(), entry->visibility,
+                                                 context->GetDiagnostics())) {
+          return false;
+        }
+
+        auto v = entry->visibility;
+        v.staged_api = true;
+        if (entry->staged_id && !assigned_ids.ReserveId(name, entry->staged_id.value().id, v,
+                                                        context->GetDiagnostics())) {
+          return false;
         }
 
         if (assigned_id_map_) {
@@ -237,7 +242,7 @@
   if (type_id_ != id.type_id()) {
     // Currently there cannot be multiple type ids for a single type.
     std::stringstream error;
-    error << "type '" << name.type << "' already has ID " << std::hex << (int)id.type_id();
+    error << "type '" << name.type << "' already has ID " << std::hex << (int)type_id_;
     return unexpected(error.str());
   }
 
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index f1b350f..2ec01cd 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -254,6 +254,12 @@
         }
         break;
 
+      case android::RES_TABLE_STAGED_ALIAS_TYPE:
+        if (!ParseOverlayable(parser.chunk())) {
+          return false;
+        }
+        break;
+
       default:
         diag_->Warn(DiagMessage(source_)
                     << "unexpected chunk type "
@@ -489,6 +495,52 @@
   return true;
 }
 
+bool BinaryResourceParser::ParseStagedAliases(const ResChunk_header* chunk) {
+  auto header = ConvertTo<ResTable_staged_alias_header>(chunk);
+  if (!header) {
+    diag_->Error(DiagMessage(source_) << "corrupt ResTable_staged_alias_header chunk");
+    return false;
+  }
+
+  const auto ref_begin = reinterpret_cast<const ResTable_staged_alias_entry*>(
+      ((uint8_t*)header) + util::DeviceToHost32(header->header.headerSize));
+  const auto ref_end = ref_begin + util::DeviceToHost32(header->count);
+  for (auto ref_iter = ref_begin; ref_iter != ref_end; ++ref_iter) {
+    const auto staged_id = ResourceId(util::DeviceToHost32(ref_iter->stagedResId));
+    const auto finalized_id = ResourceId(util::DeviceToHost32(ref_iter->finalizedResId));
+
+    // If the staged alias chunk comes before the type chunks, the resource ids and resource name
+    // pairing will not exist at this point.
+    const auto iter = id_index_.find(finalized_id);
+    if (iter == id_index_.cend()) {
+      diag_->Error(DiagMessage(source_) << "failed to find resource name for finalized"
+                                        << " resource ID " << finalized_id);
+      return false;
+    }
+
+    // Set the staged if of the finalized resource.
+    const auto& resource_name = iter->second;
+    const StagedId staged_id_def{.id = staged_id};
+    if (!table_->AddResource(NewResourceBuilder(resource_name)
+                                 .SetId(finalized_id, OnIdConflict::CREATE_ENTRY)
+                                 .SetStagedId(staged_id_def)
+                                 .SetAllowMangled(true)
+                                 .Build(),
+                             diag_)) {
+      return false;
+    }
+
+    // Since a the finalized resource entry is cloned and added to the resource table under the
+    // staged resource id, remove the cloned resource entry from the table.
+    if (!table_->RemoveResource(resource_name, staged_id)) {
+      diag_->Error(DiagMessage(source_) << "failed to find resource entry for staged "
+                                        << " resource ID " << staged_id);
+      return false;
+    }
+  }
+  return true;
+}
+
 std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& name,
                                                        const ConfigDescription& config,
                                                        const android::Res_value& value) {
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index 13dd982..cd71d16 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -57,6 +57,7 @@
                  uint8_t package_id);
   bool ParseLibrary(const android::ResChunk_header* chunk);
   bool ParseOverlayable(const android::ResChunk_header* chunk);
+  bool ParseStagedAliases(const android::ResChunk_header* chunk);
 
   std::unique_ptr<Item> ParseValue(const ResourceNameRef& name,
                                    const android::ConfigDescription& config,
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 74ecf47..a9192e8 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -72,7 +72,7 @@
 }
 
 struct FlatEntry {
-  const ResourceEntry* entry;
+  const ResourceTableEntryView* entry;
   const Value* value;
 
   // The entry string pool index to the entry's name.
@@ -286,6 +286,10 @@
       return false;
     }
 
+    if (!FlattenAliases(buffer)) {
+      return false;
+    }
+
     pkg_writer.Finish();
     return true;
   }
@@ -351,8 +355,8 @@
 
     BigBuffer values_buffer(512);
     for (FlatEntry& flat_entry : *entries) {
-      CHECK(static_cast<size_t>(flat_entry.entry->id.value().entry_id()) < num_total_entries);
-      offsets[flat_entry.entry->id.value().entry_id()] = values_buffer.size();
+      CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
+      offsets[flat_entry.entry->id.value()] = values_buffer.size();
       if (!FlattenValue(&flat_entry, &values_buffer)) {
         diag_->Error(DiagMessage()
                      << "failed to flatten resource '"
@@ -404,6 +408,26 @@
     return true;
   }
 
+  bool FlattenAliases(BigBuffer* buffer) {
+    if (aliases_.empty()) {
+      return true;
+    }
+
+    ChunkWriter alias_writer(buffer);
+    auto header =
+        alias_writer.StartChunk<ResTable_staged_alias_header>(RES_TABLE_STAGED_ALIAS_TYPE);
+    header->count = util::HostToDevice32(aliases_.size());
+
+    auto mapping = alias_writer.NextBlock<ResTable_staged_alias_entry>(aliases_.size());
+    for (auto& p : aliases_) {
+      mapping->stagedResId = util::HostToDevice32(p.first);
+      mapping->finalizedResId = util::HostToDevice32(p.second);
+      ++mapping;
+    }
+    alias_writer.Finish();
+    return true;
+  }
+
   bool FlattenOverlayable(BigBuffer* buffer) {
     std::set<ResourceId> seen_ids;
     std::map<std::string, OverlayableChunk> overlayable_chunks;
@@ -413,18 +437,17 @@
       CHECK(bool(type.id)) << "type must have an ID set when flattening <overlayable>";
       for (auto& entry : type.entries) {
         CHECK(bool(type.id)) << "entry must have an ID set when flattening <overlayable>";
-        if (!entry->overlayable_item) {
+        if (!entry.overlayable_item) {
           continue;
         }
 
-        const OverlayableItem& item = entry->overlayable_item.value();
+        const OverlayableItem& item = entry.overlayable_item.value();
 
         // Resource ids should only appear once in the resource table
-        ResourceId id =
-            android::make_resid(package_.id.value(), type.id.value(), entry->id.value().entry_id());
+        ResourceId id = android::make_resid(package_.id.value(), type.id.value(), entry.id.value());
         CHECK(seen_ids.find(id) == seen_ids.end())
             << "multiple overlayable definitions found for resource "
-            << ResourceName(package_.name, type.type, entry->name).to_string();
+            << ResourceName(package_.name, type.type, entry.name).to_string();
         seen_ids.insert(id);
 
         // Find the overlayable chunk with the specified name
@@ -452,9 +475,8 @@
 
         if (item.policies == 0) {
           context_->GetDiagnostics()->Error(DiagMessage(item.overlayable->source)
-                                                << "overlayable "
-                                                << entry->name
-                                                << " does not specify policy");
+                                            << "overlayable " << entry.name
+                                            << " does not specify policy");
           return false;
         }
 
@@ -520,7 +542,8 @@
   }
 
   bool FlattenTypeSpec(const ResourceTableTypeView& type,
-                       const std::vector<const ResourceEntry*>& sorted_entries, BigBuffer* buffer) {
+                       const std::vector<ResourceTableEntryView>& sorted_entries,
+                       BigBuffer* buffer) {
     ChunkWriter type_spec_writer(buffer);
     ResTable_typeSpec* spec_header =
         type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
@@ -534,7 +557,7 @@
     // We can't just take the size of the vector. There may be holes in the
     // entry ID space.
     // Since the entries are sorted by ID, the last one will be the biggest.
-    const size_t num_entries = sorted_entries.back()->id.value().entry_id() + 1;
+    const size_t num_entries = sorted_entries.back().id.value() + 1;
 
     spec_header->entryCount = util::HostToDevice32(num_entries);
 
@@ -542,23 +565,23 @@
     // show for which configuration axis the resource changes.
     uint32_t* config_masks = type_spec_writer.NextBlock<uint32_t>(num_entries);
 
-    for (const ResourceEntry* entry : sorted_entries) {
-      const uint16_t entry_id = entry->id.value().entry_id();
+    for (const ResourceTableEntryView& entry : sorted_entries) {
+      const uint16_t entry_id = entry.id.value();
 
       // Populate the config masks for this entry.
       uint32_t& entry_config_masks = config_masks[entry_id];
-      if (entry->visibility.level == Visibility::Level::kPublic) {
+      if (entry.visibility.level == Visibility::Level::kPublic) {
         entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
       }
-      if (entry->visibility.staged_api) {
+      if (entry.visibility.staged_api) {
         entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_STAGED_API);
       }
 
-      const size_t config_count = entry->values.size();
+      const size_t config_count = entry.values.size();
       for (size_t i = 0; i < config_count; i++) {
-        const ConfigDescription& config = entry->values[i]->config;
+        const ConfigDescription& config = entry.values[i]->config;
         for (size_t j = i + 1; j < config_count; j++) {
-          config_masks[entry_id] |= util::HostToDevice32(config.diff(entry->values[j]->config));
+          config_masks[entry_id] |= util::HostToDevice32(config.diff(entry.values[j]->config));
         }
       }
     }
@@ -590,7 +613,7 @@
       }
 
       // Since the entries are sorted by ID, the last ID will be the largest.
-      const size_t num_entries = type.entries.back()->id.value().entry_id() + 1;
+      const size_t num_entries = type.entries.back().id.value() + 1;
 
       // The binary resource table lists resource entries for each
       // configuration.
@@ -603,20 +626,26 @@
       // hardcoded string uses characters which make it an invalid resource name
       const std::string obfuscated_resource_name = "0_resource_name_obfuscated";
 
-      for (const ResourceEntry* entry : type.entries) {
+      for (const ResourceTableEntryView& entry : type.entries) {
+        if (entry.staged_id) {
+          aliases_.insert(std::make_pair(
+              entry.staged_id.value().id.id,
+              ResourceId(package_.id.value(), type.id.value(), entry.id.value()).id));
+        }
+
         uint32_t local_key_index;
-        ResourceName resource_name({}, type.type, entry->name);
+        ResourceName resource_name({}, type.type, entry.name);
         if (!collapse_key_stringpool_ ||
             name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
-          local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
+          local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
         } else {
           // resource isn't exempt from collapse, add it as obfuscated value
           local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
         }
         // Group values by configuration.
-        for (auto& config_value : entry->values) {
+        for (auto& config_value : entry.values) {
           config_to_entry_list_map[config_value->config].push_back(
-              FlatEntry{entry, config_value->value.get(), local_key_index});
+              FlatEntry{&entry, config_value->value.get(), local_key_index});
         }
       }
 
@@ -667,6 +696,7 @@
   StringPool key_pool_;
   bool collapse_key_stringpool_;
   const std::set<ResourceName>& name_collapse_exemptions_;
+  std::map<uint32_t, uint32_t> aliases_;
 };
 
 }  // namespace
@@ -684,7 +714,8 @@
   });
 
   // Write the ResTable header.
-  const auto& table_view = table->GetPartitionedView();
+  const auto& table_view =
+      table->GetPartitionedView(ResourceTableViewOptions{.create_alias_entries = true});
   ChunkWriter table_writer(buffer_);
   ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
   table_header->packageCount = util::HostToDevice32(table_view.packages.size());
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index ec331df..236c381 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -498,10 +498,20 @@
                                               out_error)) {
           return false;
         }
-
         entry->overlayable_item = std::move(overlayable_item);
       }
 
+      if (pb_entry.has_staged_id()) {
+        const pb::StagedId& pb_staged_id = pb_entry.staged_id();
+
+        StagedId staged_id;
+        if (pb_staged_id.has_source()) {
+          DeserializeSourceFromPb(pb_staged_id.source(), src_pool, &staged_id.source);
+        }
+        staged_id.id = pb_staged_id.staged_id();
+        entry->staged_id = std::move(staged_id);
+      }
+
       ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),
                        pb_entry.entry_id().id());
       if (resid.is_valid()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index d2f0336..6042ba8 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -364,43 +364,52 @@
       static const char* obfuscated_resource_name = "0_resource_name_obfuscated";
       for (const auto& entry : type.entries) {
         pb::Entry* pb_entry = pb_type->add_entry();
-        if (entry->id) {
-          pb_entry->mutable_entry_id()->set_id(entry->id.value().entry_id());
+        if (entry.id) {
+          pb_entry->mutable_entry_id()->set_id(entry.id.value());
         }
-        ResourceName resource_name({}, type.type, entry->name);
+        ResourceName resource_name({}, type.type, entry.name);
         if (options.collapse_key_stringpool &&
             options.name_collapse_exemptions.find(resource_name) ==
             options.name_collapse_exemptions.end()) {
           pb_entry->set_name(obfuscated_resource_name);
         } else {
-          pb_entry->set_name(entry->name);
+          pb_entry->set_name(entry.name);
         }
 
         // Write the Visibility struct.
         pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
-        pb_visibility->set_staged_api(entry->visibility.staged_api);
-        pb_visibility->set_level(SerializeVisibilityToPb(entry->visibility.level));
+        pb_visibility->set_staged_api(entry.visibility.staged_api);
+        pb_visibility->set_level(SerializeVisibilityToPb(entry.visibility.level));
         if (source_pool != nullptr) {
-          SerializeSourceToPb(entry->visibility.source, source_pool.get(),
+          SerializeSourceToPb(entry.visibility.source, source_pool.get(),
                               pb_visibility->mutable_source());
         }
-        pb_visibility->set_comment(entry->visibility.comment);
+        pb_visibility->set_comment(entry.visibility.comment);
 
-        if (entry->allow_new) {
+        if (entry.allow_new) {
           pb::AllowNew* pb_allow_new = pb_entry->mutable_allow_new();
           if (source_pool != nullptr) {
-            SerializeSourceToPb(entry->allow_new.value().source, source_pool.get(),
+            SerializeSourceToPb(entry.allow_new.value().source, source_pool.get(),
                                 pb_allow_new->mutable_source());
           }
-          pb_allow_new->set_comment(entry->allow_new.value().comment);
+          pb_allow_new->set_comment(entry.allow_new.value().comment);
         }
 
-        if (entry->overlayable_item) {
-          SerializeOverlayableItemToPb(entry->overlayable_item.value(), overlayables,
+        if (entry.overlayable_item) {
+          SerializeOverlayableItemToPb(entry.overlayable_item.value(), overlayables,
                                        source_pool.get(), pb_entry, out_table);
         }
 
-        for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
+        if (entry.staged_id) {
+          pb::StagedId* pb_staged_id = pb_entry->mutable_staged_id();
+          if (source_pool != nullptr) {
+            SerializeSourceToPb(entry.staged_id.value().source, source_pool.get(),
+                                pb_staged_id->mutable_source());
+          }
+          pb_staged_id->set_staged_id(entry.staged_id.value().id.id);
+        }
+
+        for (const ResourceConfigValue* config_value : entry.values) {
           pb::ConfigValue* pb_config_value = pb_entry->add_config_value();
           SerializeConfig(config_value->config, pb_config_value->mutable_config());
           pb_config_value->mutable_config()->set_product(config_value->product);
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index e563eda..38c811f 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -928,4 +928,27 @@
   EXPECT_THAT(deserialized->alias_namespaces, Eq(original->alias_namespaces));
 }
 
+TEST(ProtoSerializeTest, StagedId) {
+  CloningValueTransformer cloner(nullptr);
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+                                             .Add(NewResourceBuilder("com.app.a:string/foo")
+                                                      .SetStagedId(StagedId{.id = 0x01ff0001})
+                                                      .Build())
+                                             .Build();
+
+  ResourceTable new_table;
+  pb::ResourceTable pb_table;
+  MockFileCollection files;
+  std::string error;
+  SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
+  ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
+  EXPECT_THAT(error, IsEmpty());
+
+  auto result = new_table.FindResource(test::ParseNameOrDie("com.app.a:string/foo"));
+  ASSERT_TRUE(result);
+  ASSERT_TRUE(result.value().entry->staged_id);
+  EXPECT_THAT(result.value().entry->staged_id.value().id, Eq(ResourceId(0x01ff0001)));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index bc93ec6..22f4d18 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -151,6 +151,18 @@
     dst_entry->overlayable_item = std::move(src_entry->overlayable_item);
   }
 
+  if (src_entry->staged_id) {
+    if (dst_entry->staged_id &&
+        dst_entry->staged_id.value().id != src_entry->staged_id.value().id) {
+      context->GetDiagnostics()->Error(DiagMessage(src_entry->staged_id.value().source)
+                                       << "conflicting staged id declaration for resource '"
+                                       << src_entry->name << "'");
+      context->GetDiagnostics()->Error(DiagMessage(dst_entry->staged_id.value().source)
+                                       << "previous declaration here");
+    }
+    dst_entry->staged_id = std::move(src_entry->staged_id);
+  }
+
   return true;
 }
 
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index f94f0fe..285e5a1 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -80,9 +80,6 @@
 }
 
 void TestDirectoryFixture::WriteFile(const std::string& path, const std::string& contents) {
-  CHECK(util::StartsWith(path, temp_dir_))
-      << "Attempting to create a file outside of test temporary directory.";
-
   // Create any intermediate directories specified in the path
   auto pos = std::find(path.rbegin(), path.rend(), file::sDirSep);
   if (pos != path.rend()) {