Merge "Camera: Bump up the extension jpeg processor blob size"
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb
index 6b3278f..4f5b620 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "8572644"
+    build_id: "9653376"
     target: "CtsShim"
     source_file: "aosp_arm64/CtsShimPriv.apk"
   }
@@ -8,7 +8,7 @@
   version: ""
   version_group: ""
   git_project: "platform/frameworks/base"
-  git_branch: "tm-dev"
+  git_branch: "master"
   transform: TRANSFORM_NONE
   transform_options {
   }
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb
index 34c9c4d..404bcac 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "8572644"
+    build_id: "9653376"
     target: "CtsShim"
     source_file: "aosp_arm64/CtsShim.apk"
   }
@@ -8,7 +8,7 @@
   version: ""
   version_group: ""
   git_project: "platform/frameworks/base"
-  git_branch: "tm-dev"
+  git_branch: "master"
   transform: TRANSFORM_NONE
   transform_options {
   }
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShimPriv_apk.asciipb
new file mode 100644
index 0000000..e898091
--- /dev/null
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShimPriv_apk.asciipb
@@ -0,0 +1,15 @@
+drops {
+  android_build_drop {
+    build_id: "9653376"
+    target: "CtsShim"
+    source_file: "aosp_riscv64/CtsShimPriv.apk"
+  }
+  dest_file: "packages/CtsShim/apk//riscv64/CtsShimPriv.apk"
+  version: ""
+  version_group: ""
+  git_project: "platform/frameworks/base"
+  git_branch: "master"
+  transform: TRANSFORM_NONE
+  transform_options {
+  }
+}
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShim_apk.asciipb
new file mode 100644
index 0000000..04092366
--- /dev/null
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShim_apk.asciipb
@@ -0,0 +1,15 @@
+drops {
+  android_build_drop {
+    build_id: "9653376"
+    target: "CtsShim"
+    source_file: "aosp_riscv64/CtsShim.apk"
+  }
+  dest_file: "packages/CtsShim/apk//riscv64/CtsShim.apk"
+  version: ""
+  version_group: ""
+  git_project: "platform/frameworks/base"
+  git_branch: "master"
+  transform: TRANSFORM_NONE
+  transform_options {
+  }
+}
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb
index 6897a02..045af02 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "8572644"
+    build_id: "9653376"
     target: "CtsShim"
     source_file: "aosp_x86_64/CtsShimPriv.apk"
   }
@@ -8,7 +8,7 @@
   version: ""
   version_group: ""
   git_project: "platform/frameworks/base"
-  git_branch: "tm-dev"
+  git_branch: "master"
   transform: TRANSFORM_NONE
   transform_options {
   }
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb
index 6dfa7810..483b2f17 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "8572644"
+    build_id: "9653376"
     target: "CtsShim"
     source_file: "aosp_x86_64/CtsShim.apk"
   }
@@ -8,7 +8,7 @@
   version: ""
   version_group: ""
   git_project: "platform/frameworks/base"
-  git_branch: "tm-dev"
+  git_branch: "master"
   transform: TRANSFORM_NONE
   transform_options {
   }
diff --git a/TEST_MAPPING b/TEST_MAPPING
index a48ce0c..0ba24d1 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -73,14 +73,6 @@
       ]
     },
     {
-      "name": "TestablesTests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
-    },
-    {
       "name": "FrameworksCoreTests",
       "options": [
         {
diff --git a/apct-tests/perftests/blobstore/Android.bp b/apct-tests/perftests/blobstore/Android.bp
index 9064b44..2590fe3 100644
--- a/apct-tests/perftests/blobstore/Android.bp
+++ b/apct-tests/perftests/blobstore/Android.bp
@@ -29,7 +29,7 @@
         "androidx.test.rules",
         "androidx.annotation_annotation",
         "apct-perftests-utils",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "collector-device-lib-platform",
         "androidx.benchmark_benchmark-macro",
     ],
diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java
index 0208dab..4e4780f 100644
--- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java
+++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java
@@ -21,10 +21,10 @@
 import android.os.ParcelFileDescriptor;
 import android.perftests.utils.TraceMarkParser;
 import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
-import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
 
 import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
 
 import java.io.BufferedReader;
 import java.io.IOException;
diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
index 03e5468..3cd9f50 100644
--- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
+++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
@@ -25,12 +25,12 @@
 import android.perftests.utils.PerfManualStatusReporter;
 import android.perftests.utils.TraceMarkParser;
 import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
-import android.support.test.uiautomator.UiDevice;
 import android.util.DataUnit;
 
 import androidx.benchmark.macro.MacrobenchmarkScope;
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
 
 import com.android.utils.blob.FakeBlobData;
 
diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp
index c967e51..45c6b8c 100644
--- a/apct-tests/perftests/multiuser/Android.bp
+++ b/apct-tests/perftests/multiuser/Android.bp
@@ -31,6 +31,9 @@
     ],
     platform_apis: true,
     test_suites: ["device-tests"],
-    data: ["trace_configs/*"],
+    data: [
+        ":MultiUserPerfDummyApp",
+        "trace_configs/*",
+    ],
     certificate: "platform",
 }
diff --git a/apct-tests/perftests/packagemanager/Android.bp b/apct-tests/perftests/packagemanager/Android.bp
index 81cec91..b6ea54d 100644
--- a/apct-tests/perftests/packagemanager/Android.bp
+++ b/apct-tests/perftests/packagemanager/Android.bp
@@ -33,7 +33,59 @@
 
     test_suites: ["device-tests"],
 
-    data: [":perfetto_artifacts"],
+    data: [
+        ":QueriesAll4",
+        ":QueriesAll31",
+        ":QueriesAll43",
+        ":QueriesAll15",
+        ":QueriesAll27",
+        ":QueriesAll39",
+        ":QueriesAll11",
+        ":QueriesAll23",
+        ":QueriesAll35",
+        ":QueriesAll47",
+        ":QueriesAll9",
+        ":QueriesAll19",
+        ":QueriesAll1",
+        ":QueriesAll5",
+        ":QueriesAll40",
+        ":QueriesAll20",
+        ":QueriesAll32",
+        ":QueriesAll48",
+        ":QueriesAll16",
+        ":QueriesAll28",
+        ":QueriesAll44",
+        ":QueriesAll12",
+        ":QueriesAll24",
+        ":QueriesAll36",
+        ":QueriesAll6",
+        ":QueriesAll2",
+        ":QueriesAll41",
+        ":QueriesAll21",
+        ":QueriesAll37",
+        ":QueriesAll49",
+        ":QueriesAll17",
+        ":QueriesAll29",
+        ":QueriesAll33",
+        ":QueriesAll45",
+        ":QueriesAll13",
+        ":QueriesAll25",
+        ":QueriesAll7",
+        ":QueriesAll3",
+        ":QueriesAll30",
+        ":QueriesAll42",
+        ":QueriesAll10",
+        ":QueriesAll26",
+        ":QueriesAll38",
+        ":QueriesAll18",
+        ":QueriesAll22",
+        ":QueriesAll34",
+        ":QueriesAll46",
+        ":QueriesAll14",
+        ":QueriesAll8",
+        ":QueriesAll0",
+        ":perfetto_artifacts",
+    ],
 
     certificate: "platform",
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 60afdc7..c6fd0bf 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -906,6 +906,10 @@
                     synchronized (mLock) {
                         mUidToPackageCache.remove(uid);
                     }
+                } else {
+                    synchronized (mJobSchedulerStub.mPersistCache) {
+                        mJobSchedulerStub.mPersistCache.remove(pkgUid);
+                    }
                 }
             } else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
                 if (DEBUG) {
diff --git a/boot/OWNERS b/boot/OWNERS
index 0e258d0..3fe1a41 100644
--- a/boot/OWNERS
+++ b/boot/OWNERS
@@ -2,5 +2,5 @@
 file:platform/build/soong:/OWNERS
 
 # art-team@ manages the boot image profiles for frameworks
-per-file boot-* = calin@google.com, yawanng@google.com, ngeoffray@google.com
-per-file preloaded-classes* = calin@google.com, yawanng@google.com, ngeoffray@google.com
+per-file boot-* = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com
+per-file preloaded-classes* = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 6b6bc97..27dadda 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -107,6 +107,7 @@
 static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
 static const char CLOCK_ENABLED_PROP_NAME[] = "persist.sys.bootanim.clock.enabled";
 static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
+static const int MAX_CHECK_EXIT_INTERVAL_US = 50000;
 static constexpr size_t TEXT_POS_LEN_MAX = 16;
 static const int DYNAMIC_COLOR_COUNT = 4;
 static const char U_TEXTURE[] = "uTexture";
@@ -1680,7 +1681,17 @@
                 checkExit();
             }
 
-            usleep(part.pause * ns2us(frameDuration));
+            int pauseDuration = part.pause * ns2us(frameDuration);
+            while(pauseDuration > 0 && !exitPending()){
+                if (pauseDuration > MAX_CHECK_EXIT_INTERVAL_US) {
+                    usleep(MAX_CHECK_EXIT_INTERVAL_US);
+                    pauseDuration -= MAX_CHECK_EXIT_INTERVAL_US;
+                } else {
+                    usleep(pauseDuration);
+                    break;
+                }
+                checkExit();
+            }
 
             if (exitPending() && !part.count && mCurrentInset >= mTargetInset &&
                 !part.hasFadingPhase()) {
diff --git a/config/OWNERS b/config/OWNERS
index 74813bc..6a5df76 100644
--- a/config/OWNERS
+++ b/config/OWNERS
@@ -1,8 +1,8 @@
 include /ZYGOTE_OWNERS
 
 # art-team@ manages the boot image profiles
-per-file boot-* = ngeoffray@google.com, vmarko@google.com
-per-file dirty-image-objects = ngeoffray@google.com, vmarko@google.com
-per-file generate-preloaded-classes.sh = ngeoffray@google.com, vmarko@google.com
-per-file preloaded-classes* = ngeoffray@google.com, vmarko@google.com
+per-file boot-* = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com
+per-file dirty-image-objects = ishcheikin@google.com, ngeoffray@google.com, vmarko@google.com
+per-file generate-preloaded-classes.sh = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com
+per-file preloaded-classes* = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com
 
diff --git a/core/api/current.txt b/core/api/current.txt
index a8552f8..cf3938a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18727,8 +18727,8 @@
     method public int bulkTransfer(android.hardware.usb.UsbEndpoint, byte[], int, int, int);
     method public boolean claimInterface(android.hardware.usb.UsbInterface, boolean);
     method public void close();
-    method public int controlTransfer(int, int, int, int, byte[], int, int);
-    method public int controlTransfer(int, int, int, int, byte[], int, int, int);
+    method public int controlTransfer(int, int, int, int, @Nullable byte[], int, int);
+    method public int controlTransfer(int, int, int, int, @Nullable byte[], int, int, int);
     method public int getFileDescriptor();
     method public byte[] getRawDescriptors();
     method public String getSerial();
@@ -27503,24 +27503,15 @@
 
   public final class NfcAdapter {
     method public void disableForegroundDispatch(android.app.Activity);
-    method @Deprecated public void disableForegroundNdefPush(android.app.Activity);
     method public void disableReaderMode(android.app.Activity);
     method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
-    method @Deprecated public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
     method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
     method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
     method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
     method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
-    method @Deprecated public boolean invokeBeam(android.app.Activity);
     method public boolean isEnabled();
-    method @Deprecated public boolean isNdefPushEnabled();
     method public boolean isSecureNfcEnabled();
     method public boolean isSecureNfcSupported();
-    method @Deprecated public void setBeamPushUris(android.net.Uri[], android.app.Activity);
-    method @Deprecated public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
-    method @Deprecated public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
-    method @Deprecated public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
-    method @Deprecated public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
     field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
     field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
     field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
@@ -41371,6 +41362,8 @@
     field public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY = "carrier_nr_availabilities_int_array";
     field public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL = "carrier_provisions_wifi_merged_networks_bool";
     field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
+    field public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array";
+    field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
     field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
     field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
     field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
@@ -41411,6 +41404,7 @@
     field public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL = "data_limit_notification_bool";
     field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
     field public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL = "data_rapid_notification_bool";
+    field public static final String KEY_DATA_SWITCH_VALIDATION_MIN_INTERVAL_MILLIS_LONG = "data_switch_validation_min_gap_long";
     field public static final String KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG = "data_switch_validation_timeout_long";
     field public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL = "data_warning_notification_bool";
     field public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
@@ -42752,9 +42746,12 @@
     method public int getDomain();
     method @Nullable public String getRegisteredPlmn();
     method public int getTransportType();
-    method public boolean isRegistered();
-    method public boolean isRoaming();
-    method public boolean isSearching();
+    method public boolean isNetworkRegistered();
+    method public boolean isNetworkRoaming();
+    method public boolean isNetworkSearching();
+    method @Deprecated public boolean isRegistered();
+    method @Deprecated public boolean isRoaming();
+    method @Deprecated public boolean isSearching();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.NetworkRegistrationInfo> CREATOR;
     field public static final int DOMAIN_CS = 1; // 0x1
@@ -43311,6 +43308,7 @@
     method public int getActiveSubscriptionInfoCountMax();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getActiveSubscriptionInfoList();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public java.util.List<android.telephony.SubscriptionInfo> getAllSubscriptionInfoList();
     method @NonNull public java.util.List<android.telephony.SubscriptionInfo> getCompleteActiveSubscriptionInfoList();
     method public static int getDefaultDataSubscriptionId();
     method public static int getDefaultSmsSubscriptionId();
@@ -43322,7 +43320,8 @@
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int, int);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int);
     method public static int getSlotIndex(int);
-    method @Nullable public int[] getSubscriptionIds(int);
+    method public static int getSubscriptionId(int);
+    method @Deprecated @Nullable public int[] getSubscriptionIds(int);
     method @NonNull public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getSubscriptionsInGroup(@NonNull android.os.ParcelUuid);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isActiveSubscriptionId(int);
@@ -43506,6 +43505,7 @@
     method public int describeContents();
     method public int getNetworkType();
     method public int getOverrideNetworkType();
+    method public boolean isRoaming();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.TelephonyDisplayInfo> CREATOR;
     field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 1fa1e89..8b3696a 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -252,6 +252,22 @@
 
 }
 
+package android.nfc {
+
+  public final class NfcAdapter {
+    method @Deprecated public void disableForegroundNdefPush(android.app.Activity);
+    method @Deprecated public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
+    method @Deprecated public boolean invokeBeam(android.app.Activity);
+    method @Deprecated public boolean isNdefPushEnabled();
+    method @Deprecated public void setBeamPushUris(android.net.Uri[], android.app.Activity);
+    method @Deprecated public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
+    method @Deprecated public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
+    method @Deprecated public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
+    method @Deprecated public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
+  }
+
+}
+
 package android.os {
 
   public class BatteryManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 9882a4f..821944f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -9112,9 +9112,7 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
@@ -9123,10 +9121,8 @@
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
-    method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
-    field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
     field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
     field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
     field public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2; // 0xfffffffe
@@ -12832,7 +12828,8 @@
 
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
     method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
-    method public int getRegistrationState();
+    method public int getNetworkRegistrationState();
+    method @Deprecated public int getRegistrationState();
     method public int getRejectCause();
     method public int getRoamingType();
     method public boolean isEmergencyEnabled();
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 2c5acf1..1c10356 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -140,6 +140,17 @@
 
 }
 
+package android.nfc {
+
+  public final class NfcAdapter {
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
+    method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
+    field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
+  }
+
+}
+
 package android.os {
 
   public class Build {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7cfb66a..6a5ef3a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -141,8 +141,9 @@
     field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2
     field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1
     field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4
-    field public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8
+    field @Deprecated public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8
     field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0
+    field public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 8; // 0x8
     field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
     field public static final int PROCESS_STATE_TOP = 2; // 0x2
     field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index eadf758..d328b34 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -643,9 +643,17 @@
     @TestApi
     public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2;
 
-    /** @hide Process can access network despite any power saving resrictions */
+    /** @hide Process can access network despite any power saving restrictions */
     @TestApi
-    public static final int PROCESS_CAPABILITY_NETWORK = 1 << 3;
+    public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 1 << 3;
+    /**
+     * @hide
+     * @deprecated Use {@link #PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK} instead.
+     */
+    @TestApi
+    @Deprecated
+    public static final int PROCESS_CAPABILITY_NETWORK =
+            PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
 
     /** @hide all capabilities, the ORing of all flags in {@link ProcessCapability}*/
     @TestApi
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5599893..5ba7a4c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -238,6 +238,7 @@
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Method;
 import java.net.InetAddress;
+import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
@@ -4221,18 +4222,20 @@
 
     static void handleAttachStartupAgents(String dataDir) {
         try {
-            Path code_cache = ContextImpl.getCodeCacheDirBeforeBind(new File(dataDir)).toPath();
-            if (!Files.exists(code_cache)) {
+            Path codeCache = ContextImpl.getCodeCacheDirBeforeBind(new File(dataDir)).toPath();
+            if (!Files.exists(codeCache)) {
                 return;
             }
-            Path startup_path = code_cache.resolve("startup_agents");
-            if (Files.exists(startup_path)) {
-                for (Path p : Files.newDirectoryStream(startup_path)) {
-                    handleAttachAgent(
-                            p.toAbsolutePath().toString()
-                            + "="
-                            + dataDir,
-                            null);
+            Path startupPath = codeCache.resolve("startup_agents");
+            if (Files.exists(startupPath)) {
+                try (DirectoryStream<Path> startupFiles = Files.newDirectoryStream(startupPath)) {
+                    for (Path p : startupFiles) {
+                        handleAttachAgent(
+                                p.toAbsolutePath().toString()
+                                        + "="
+                                        + dataDir,
+                                null);
+                    }
                 }
             }
         } catch (Exception e) {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index bccbb38..2a854b2 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -37,8 +37,6 @@
 import android.os.PowerExemptionManager.ReasonCode;
 import android.os.PowerExemptionManager.TempAllowListType;
 
-import com.android.internal.util.Preconditions;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
@@ -61,7 +59,8 @@
     private long mRequireCompatChangeId = CHANGE_INVALID;
     private long mIdForResponseEvent;
     private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
-    private @Nullable String mDeliveryGroupMatchingKey;
+    private @Nullable String mDeliveryGroupMatchingNamespaceFragment;
+    private @Nullable String mDeliveryGroupMatchingKeyFragment;
     private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
     private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
     private @DeferralPolicy int mDeferralPolicy;
@@ -209,7 +208,13 @@
             "android:broadcast.deliveryGroupPolicy";
 
     /**
-     * Corresponds to {@link #setDeliveryGroupMatchingKey(String, String)}.
+     * Corresponds to namespace fragment of {@link #setDeliveryGroupMatchingKey(String, String)}.
+     */
+    private static final String KEY_DELIVERY_GROUP_NAMESPACE =
+            "android:broadcast.deliveryGroupMatchingNamespace";
+
+    /**
+     * Corresponds to key fragment of {@link #setDeliveryGroupMatchingKey(String, String)}.
      */
     private static final String KEY_DELIVERY_GROUP_KEY =
             "android:broadcast.deliveryGroupMatchingKey";
@@ -350,7 +355,8 @@
         mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
         mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
                 DELIVERY_GROUP_POLICY_ALL);
-        mDeliveryGroupMatchingKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
+        mDeliveryGroupMatchingNamespaceFragment = opts.getString(KEY_DELIVERY_GROUP_NAMESPACE);
+        mDeliveryGroupMatchingKeyFragment = opts.getString(KEY_DELIVERY_GROUP_KEY);
         mDeliveryGroupExtrasMerger = opts.getParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
                 BundleMerger.class);
         mDeliveryGroupMatchingFilter = opts.getParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER,
@@ -864,11 +870,8 @@
     @NonNull
     public BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String namespace,
             @NonNull String key) {
-        Preconditions.checkArgument(!namespace.contains(":"),
-                "namespace should not contain ':'");
-        Preconditions.checkArgument(!key.contains(":"),
-                "key should not contain ':'");
-        mDeliveryGroupMatchingKey = namespace + ":" + key;
+        mDeliveryGroupMatchingNamespaceFragment = Objects.requireNonNull(namespace);
+        mDeliveryGroupMatchingKeyFragment = Objects.requireNonNull(key);
         return this;
     }
 
@@ -881,7 +884,38 @@
      */
     @Nullable
     public String getDeliveryGroupMatchingKey() {
-        return mDeliveryGroupMatchingKey;
+        if (mDeliveryGroupMatchingNamespaceFragment == null
+                || mDeliveryGroupMatchingKeyFragment == null) {
+            return null;
+        }
+        return String.join(":", mDeliveryGroupMatchingNamespaceFragment,
+                mDeliveryGroupMatchingKeyFragment);
+    }
+
+    /**
+     * Return the namespace fragment that is used to identify the delivery group that this
+     * broadcast belongs to.
+     *
+     * @return the delivery group namespace fragment that was previously set using
+     *         {@link #setDeliveryGroupMatchingKey(String, String)}.
+     * @hide
+     */
+    @Nullable
+    public String getDeliveryGroupMatchingNamespaceFragment() {
+        return mDeliveryGroupMatchingNamespaceFragment;
+    }
+
+    /**
+     * Return the key fragment that is used to identify the delivery group that this
+     * broadcast belongs to.
+     *
+     * @return the delivery group key fragment that was previously set using
+     *         {@link #setDeliveryGroupMatchingKey(String, String)}.
+     * @hide
+     */
+    @Nullable
+    public String getDeliveryGroupMatchingKeyFragment() {
+        return mDeliveryGroupMatchingKeyFragment;
     }
 
     /**
@@ -889,7 +923,8 @@
      * {@link #setDeliveryGroupMatchingKey(String, String)}.
      */
     public void clearDeliveryGroupMatchingKey() {
-        mDeliveryGroupMatchingKey = null;
+        mDeliveryGroupMatchingNamespaceFragment = null;
+        mDeliveryGroupMatchingKeyFragment = null;
     }
 
     /**
@@ -1101,8 +1136,11 @@
         if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
             b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
         }
-        if (mDeliveryGroupMatchingKey != null) {
-            b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKey);
+        if (mDeliveryGroupMatchingNamespaceFragment != null) {
+            b.putString(KEY_DELIVERY_GROUP_NAMESPACE, mDeliveryGroupMatchingNamespaceFragment);
+        }
+        if (mDeliveryGroupMatchingKeyFragment != null) {
+            b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKeyFragment);
         }
         if (mDeliveryGroupPolicy == DELIVERY_GROUP_POLICY_MERGED) {
             if (mDeliveryGroupExtrasMerger != null) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2ea0d82..a320f1e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -11488,7 +11488,7 @@
     private void toUriInner(StringBuilder uri, String scheme, String defAction,
             String defPackage, int flags) {
         if (scheme != null) {
-            uri.append("scheme=").append(scheme).append(';');
+            uri.append("scheme=").append(Uri.encode(scheme)).append(';');
         }
         if (mAction != null && !mAction.equals(defAction)) {
             uri.append("action=").append(Uri.encode(mAction)).append(';');
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index b1252fd..49d3cac 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.PendingIntentInfo;
+import android.app.ActivityOptions;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Bundle;
 import android.os.Handler;
@@ -158,7 +159,7 @@
      */
     public void sendIntent(Context context, int code, Intent intent,
             OnFinished onFinished, Handler handler) throws SendIntentException {
-        sendIntent(context, code, intent, onFinished, handler, null);
+        sendIntent(context, code, intent, onFinished, handler, null, null /* options */);
     }
 
     /**
@@ -190,6 +191,42 @@
     public void sendIntent(Context context, int code, Intent intent,
             OnFinished onFinished, Handler handler, String requiredPermission)
             throws SendIntentException {
+        sendIntent(context, code, intent, onFinished, handler, requiredPermission,
+                null /* options */);
+    }
+
+    /**
+     * Perform the operation associated with this IntentSender, allowing the
+     * caller to specify information about the Intent to use and be notified
+     * when the send has completed.
+     *
+     * @param context The Context of the caller.  This may be null if
+     * <var>intent</var> is also null.
+     * @param code Result code to supply back to the IntentSender's target.
+     * @param intent Additional Intent data.  See {@link Intent#fillIn
+     * Intent.fillIn()} for information on how this is applied to the
+     * original Intent.  Use null to not modify the original Intent.
+     * @param onFinished The object to call back on when the send has
+     * completed, or null for no callback.
+     * @param handler Handler identifying the thread on which the callback
+     * should happen.  If null, the callback will happen from the thread
+     * pool of the process.
+     * @param requiredPermission Name of permission that a recipient of the PendingIntent
+     * is required to hold.  This is only valid for broadcast intents, and
+     * corresponds to the permission argument in
+     * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}.
+     * If null, no permission is required.
+     * @param options Additional options the caller would like to provide to modify the sending
+     * behavior.  May be built from an {@link ActivityOptions} to apply to an activity start.
+     *
+     * @throws SendIntentException Throws CanceledIntentException if the IntentSender
+     * is no longer allowing more intents to be sent through it.
+     * @hide
+     */
+    public void sendIntent(Context context, int code, Intent intent,
+            OnFinished onFinished, Handler handler, String requiredPermission,
+            @Nullable Bundle options)
+            throws SendIntentException {
         try {
             String resolvedType = intent != null ?
                     intent.resolveTypeIfNeeded(context.getContentResolver())
@@ -199,7 +236,7 @@
                     onFinished != null
                             ? new FinishedDispatcher(this, onFinished, handler)
                             : null,
-                    requiredPermission, null);
+                    requiredPermission, options);
             if (res < 0) {
                 throw new SendIntentException();
             }
diff --git a/core/java/android/hardware/soundtrigger/OWNERS b/core/java/android/hardware/soundtrigger/OWNERS
index 01b2cb9..1e41886 100644
--- a/core/java/android/hardware/soundtrigger/OWNERS
+++ b/core/java/android/hardware/soundtrigger/OWNERS
@@ -1,2 +1 @@
-atneya@google.com
-elaurent@google.com
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index 7c2e518..44144d9 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -238,7 +238,7 @@
      * or negative value for failure
      */
     public int controlTransfer(int requestType, int request, int value,
-            int index, byte[] buffer, int length, int timeout) {
+            int index, @Nullable byte[] buffer, int length, int timeout) {
         return controlTransfer(requestType, request, value, index, buffer, 0, length, timeout);
     }
 
@@ -263,7 +263,7 @@
      * or negative value for failure
      */
     public int controlTransfer(int requestType, int request, int value, int index,
-            byte[] buffer, int offset, int length, int timeout) {
+            @Nullable byte[] buffer, int offset, int length, int timeout) {
         checkBounds(buffer, offset, length);
         return native_control_request(requestType, request, value, index,
                 buffer, offset, length, timeout);
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index a20191c..74775a8 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -769,6 +769,19 @@
         }
     }
 
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("IkeV2VpnProfile [");
+        sb.append(" MaxMtu=" + mMaxMtu);
+        if (mIsBypassable) sb.append(" Bypassable");
+        if (mRequiresInternetValidation) sb.append(" RequiresInternetValidation");
+        if (mIsRestrictedToTestNetworks) sb.append(" RestrictedToTestNetworks");
+        if (mAutomaticNattKeepaliveTimerEnabled) sb.append(" AutomaticNattKeepaliveTimerEnabled");
+        if (mAutomaticIpVersionSelectionEnabled) sb.append(" AutomaticIpVersionSelectionEnabled");
+        sb.append("]");
+        return sb.toString();
+    }
+
     /** A incremental builder for IKEv2 VPN profiles */
     public static final class Builder {
         private int mType = -1;
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
index 38b3174..46cf016 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
@@ -354,6 +354,7 @@
     }
 
     /** @hide */
+    @Override
     public Map<Integer, Integer> getCapabilitiesMatchCriteria() {
         return Collections.unmodifiableMap(new HashMap<>(mCapabilitiesMatchCriteria));
     }
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index 6f9c9dd..a27e923 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -16,6 +16,7 @@
 package android.net.vcn;
 
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -75,6 +76,7 @@
     static {
         ALLOWED_TRANSPORTS.add(TRANSPORT_WIFI);
         ALLOWED_TRANSPORTS.add(TRANSPORT_CELLULAR);
+        ALLOWED_TRANSPORTS.add(TRANSPORT_TEST);
     }
 
     private static final String PACKAGE_NAME_KEY = "mPackageName";
@@ -155,6 +157,11 @@
                                 + transport
                                 + " which might be from a new version of VcnConfig");
             }
+
+            if (transport == TRANSPORT_TEST && !mIsTestModeProfile) {
+                throw new IllegalArgumentException(
+                        "Found TRANSPORT_TEST in a non-test-mode profile");
+            }
         }
     }
 
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
index 9235d09..edf2c09 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
@@ -29,6 +29,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -307,4 +308,7 @@
     public int getMinExitDownstreamBandwidthKbps() {
         return mMinExitDownstreamBandwidthKbps;
     }
+
+    /** @hide */
+    public abstract Map<Integer, Integer> getCapabilitiesMatchCriteria();
 }
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
index 2544a6d..2e6b09f 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
@@ -15,6 +15,9 @@
  */
 package android.net.vcn;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
 import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
@@ -23,6 +26,7 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.net.NetworkCapabilities;
+import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria;
 import android.os.PersistableBundle;
 import android.util.ArraySet;
 
@@ -32,6 +36,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -162,6 +167,12 @@
         return Collections.unmodifiableSet(mSsids);
     }
 
+    /** @hide */
+    @Override
+    public Map<Integer, Integer> getCapabilitiesMatchCriteria() {
+        return Collections.singletonMap(NET_CAPABILITY_INTERNET, MATCH_REQUIRED);
+    }
+
     /** This class is used to incrementally build VcnWifiUnderlyingNetworkTemplate objects. */
     public static final class Builder {
         private int mMeteredMatchCriteria = MATCH_ANY;
diff --git a/core/java/android/nfc/BeamShareData.java b/core/java/android/nfc/BeamShareData.java
deleted file mode 100644
index 6a40f98..0000000
--- a/core/java/android/nfc/BeamShareData.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package android.nfc;
-
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.UserHandle;
-
-/**
- * Class to IPC data to be shared over Android Beam.
- * Allows bundling NdefMessage, Uris and flags in a single
- * IPC call. This is important as we want to reduce the
- * amount of IPC calls at "touch time".
- * @hide
- */
-public final class BeamShareData implements Parcelable {
-    public final NdefMessage ndefMessage;
-    public final Uri[] uris;
-    public final UserHandle userHandle;
-    public final int flags;
-
-    public BeamShareData(NdefMessage msg, Uri[] uris, UserHandle userHandle, int flags) {
-        this.ndefMessage = msg;
-        this.uris = uris;
-        this.userHandle = userHandle;
-        this.flags = flags;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        int urisLength = (uris != null) ? uris.length : 0;
-        dest.writeParcelable(ndefMessage, 0);
-        dest.writeInt(urisLength);
-        if (urisLength > 0) {
-            dest.writeTypedArray(uris, 0);
-        }
-        dest.writeParcelable(userHandle, 0);
-        dest.writeInt(this.flags);
-    }
-
-    public static final @android.annotation.NonNull Parcelable.Creator<BeamShareData> CREATOR =
-            new Parcelable.Creator<BeamShareData>() {
-        @Override
-        public BeamShareData createFromParcel(Parcel source) {
-            Uri[] uris = null;
-            NdefMessage msg = source.readParcelable(NdefMessage.class.getClassLoader(), android.nfc.NdefMessage.class);
-            int numUris = source.readInt();
-            if (numUris > 0) {
-                uris = new Uri[numUris];
-                source.readTypedArray(uris, Uri.CREATOR);
-            }
-            UserHandle userHandle = source.readParcelable(UserHandle.class.getClassLoader(), android.os.UserHandle.class);
-            int flags = source.readInt();
-
-            return new BeamShareData(msg, uris, userHandle, flags);
-        }
-
-        @Override
-        public BeamShareData[] newArray(int size) {
-            return new BeamShareData[size];
-        }
-    };
-}
diff --git a/core/java/android/nfc/IAppCallback.aidl b/core/java/android/nfc/IAppCallback.aidl
index 133146d..b06bf06 100644
--- a/core/java/android/nfc/IAppCallback.aidl
+++ b/core/java/android/nfc/IAppCallback.aidl
@@ -16,7 +16,6 @@
 
 package android.nfc;
 
-import android.nfc.BeamShareData;
 import android.nfc.Tag;
 
 /**
@@ -24,7 +23,5 @@
  */
 interface IAppCallback
 {
-    BeamShareData createBeamShareData(byte peerLlcpVersion);
-    oneway void onNdefPushComplete(byte peerLlcpVersion);
     oneway void onTagDiscovered(in Tag tag);
 }
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 8a30ef4..a6d8caf 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -18,7 +18,6 @@
 
 import android.app.PendingIntent;
 import android.content.IntentFilter;
-import android.nfc.BeamShareData;
 import android.nfc.NdefMessage;
 import android.nfc.Tag;
 import android.nfc.TechListParcel;
@@ -47,24 +46,18 @@
     int getState();
     boolean disable(boolean saveState);
     boolean enable();
-    boolean enableNdefPush();
-    boolean disableNdefPush();
-    boolean isNdefPushEnabled();
     void pausePolling(int timeoutInMs);
     void resumePolling();
 
     void setForegroundDispatch(in PendingIntent intent,
             in IntentFilter[] filters, in TechListParcel techLists);
     void setAppCallback(in IAppCallback callback);
-    oneway void invokeBeam();
-    oneway void invokeBeamInternal(in BeamShareData shareData);
 
     boolean ignore(int nativeHandle, int debounceMs, ITagRemovedCallback callback);
 
     void dispatch(in Tag tag);
 
     void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras);
-    void setP2pModes(int initatorModes, int targetModes);
 
     void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, in int[] techList);
     void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler);
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index 911aaf3..8d75cac 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -19,9 +19,6 @@
 import android.app.Activity;
 import android.app.Application;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.ContentProvider;
-import android.content.Intent;
-import android.net.Uri;
 import android.nfc.NfcAdapter.ReaderCallback;
 import android.os.Binder;
 import android.os.Bundle;
@@ -110,14 +107,8 @@
     class NfcActivityState {
         boolean resumed = false;
         Activity activity;
-        NdefMessage ndefMessage = null;  // static NDEF message
-        NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null;
-        NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
-        NfcAdapter.CreateBeamUrisCallback uriCallback = null;
-        Uri[] uris = null;
-        int flags = 0;
-        int readerModeFlags = 0;
         NfcAdapter.ReaderCallback readerCallback = null;
+        int readerModeFlags = 0;
         Bundle readerModeExtras = null;
         Binder token;
 
@@ -137,24 +128,16 @@
             unregisterApplication(activity.getApplication());
             resumed = false;
             activity = null;
-            ndefMessage = null;
-            ndefMessageCallback = null;
-            onNdefPushCompleteCallback = null;
-            uriCallback = null;
-            uris = null;
+            readerCallback = null;
             readerModeFlags = 0;
+            readerModeExtras = null;
             token = null;
         }
         @Override
         public String toString() {
-            StringBuilder s = new StringBuilder("[").append(" ");
-            s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
-            s.append(uriCallback).append(" ");
-            if (uris != null) {
-                for (Uri uri : uris) {
-                    s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
-                }
-            }
+            StringBuilder s = new StringBuilder("[");
+            s.append(readerCallback);
+            s.append("]");
             return s.toString();
         }
     }
@@ -245,92 +228,6 @@
         }
     }
 
-    public void setNdefPushContentUri(Activity activity, Uri[] uris) {
-        boolean isResumed;
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = getActivityState(activity);
-            state.uris = uris;
-            isResumed = state.resumed;
-        }
-        if (isResumed) {
-            // requestNfcServiceCallback() verifies permission also
-            requestNfcServiceCallback();
-        } else {
-            // Crash API calls early in case NFC permission is missing
-            verifyNfcPermission();
-        }
-    }
-
-
-    public void setNdefPushContentUriCallback(Activity activity,
-            NfcAdapter.CreateBeamUrisCallback callback) {
-        boolean isResumed;
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = getActivityState(activity);
-            state.uriCallback = callback;
-            isResumed = state.resumed;
-        }
-        if (isResumed) {
-            // requestNfcServiceCallback() verifies permission also
-            requestNfcServiceCallback();
-        } else {
-            // Crash API calls early in case NFC permission is missing
-            verifyNfcPermission();
-        }
-    }
-
-    public void setNdefPushMessage(Activity activity, NdefMessage message, int flags) {
-        boolean isResumed;
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = getActivityState(activity);
-            state.ndefMessage = message;
-            state.flags = flags;
-            isResumed = state.resumed;
-        }
-        if (isResumed) {
-            // requestNfcServiceCallback() verifies permission also
-            requestNfcServiceCallback();
-        } else {
-            // Crash API calls early in case NFC permission is missing
-            verifyNfcPermission();
-        }
-    }
-
-    public void setNdefPushMessageCallback(Activity activity,
-            NfcAdapter.CreateNdefMessageCallback callback, int flags) {
-        boolean isResumed;
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = getActivityState(activity);
-            state.ndefMessageCallback = callback;
-            state.flags = flags;
-            isResumed = state.resumed;
-        }
-        if (isResumed) {
-            // requestNfcServiceCallback() verifies permission also
-            requestNfcServiceCallback();
-        } else {
-            // Crash API calls early in case NFC permission is missing
-            verifyNfcPermission();
-        }
-    }
-
-    public void setOnNdefPushCompleteCallback(Activity activity,
-            NfcAdapter.OnNdefPushCompleteCallback callback) {
-        boolean isResumed;
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = getActivityState(activity);
-            state.onNdefPushCompleteCallback = callback;
-            isResumed = state.resumed;
-        }
-        if (isResumed) {
-            // requestNfcServiceCallback() verifies permission also
-            requestNfcServiceCallback();
-        } else {
-            // Crash API calls early in case NFC permission is missing
-            verifyNfcPermission();
-        }
-    }
-
     /**
      * Request or unrequest NFC service callbacks.
      * Makes IPC call - do not hold lock.
@@ -351,86 +248,6 @@
         }
     }
 
-    /** Callback from NFC service, usually on binder thread */
-    @Override
-    public BeamShareData createBeamShareData(byte peerLlcpVersion) {
-        NfcAdapter.CreateNdefMessageCallback ndefCallback;
-        NfcAdapter.CreateBeamUrisCallback urisCallback;
-        NdefMessage message;
-        Activity activity;
-        Uri[] uris;
-        int flags;
-        NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion);
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = findResumedActivityState();
-            if (state == null) return null;
-
-            ndefCallback = state.ndefMessageCallback;
-            urisCallback = state.uriCallback;
-            message = state.ndefMessage;
-            uris = state.uris;
-            flags = state.flags;
-            activity = state.activity;
-        }
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            // Make callbacks without lock
-            if (ndefCallback != null) {
-                message = ndefCallback.createNdefMessage(event);
-            }
-            if (urisCallback != null) {
-                uris = urisCallback.createBeamUris(event);
-                if (uris != null) {
-                    ArrayList<Uri> validUris = new ArrayList<Uri>();
-                    for (Uri uri : uris) {
-                        if (uri == null) {
-                            Log.e(TAG, "Uri not allowed to be null.");
-                            continue;
-                        }
-                        String scheme = uri.getScheme();
-                        if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
-                                !scheme.equalsIgnoreCase("content"))) {
-                            Log.e(TAG, "Uri needs to have " +
-                                    "either scheme file or scheme content");
-                            continue;
-                        }
-                        uri = ContentProvider.maybeAddUserId(uri, activity.getUserId());
-                        validUris.add(uri);
-                    }
-
-                    uris = validUris.toArray(new Uri[validUris.size()]);
-                }
-            }
-            if (uris != null && uris.length > 0) {
-                for (Uri uri : uris) {
-                    // Grant the NFC process permission to read these URIs
-                    activity.grantUriPermission("com.android.nfc", uri,
-                            Intent.FLAG_GRANT_READ_URI_PERMISSION);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-        return new BeamShareData(message, uris, activity.getUser(), flags);
-    }
-
-    /** Callback from NFC service, usually on binder thread */
-    @Override
-    public void onNdefPushComplete(byte peerLlcpVersion) {
-        NfcAdapter.OnNdefPushCompleteCallback callback;
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = findResumedActivityState();
-            if (state == null) return;
-
-            callback = state.onNdefPushCompleteCallback;
-        }
-        NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion);
-        // Make callback without lock
-        if (callback != null) {
-            callback.onNdefPushComplete(event);
-        }
-    }
-
     @Override
     public void onTagDiscovered(Tag tag) throws RemoteException {
         NfcAdapter.ReaderCallback callback;
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 656cd99..cacde7f 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -343,8 +343,12 @@
      */
     public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
 
-    /** @hide */
+    /**
+     * @hide
+     * @removed
+     */
     @SystemApi
+    @UnsupportedAppUsage
     public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 0x1;
 
     /** @hide */
@@ -418,7 +422,6 @@
     // Guarded by NfcAdapter.class
     static boolean sIsInitialized = false;
     static boolean sHasNfcFeature;
-    static boolean sHasBeamFeature;
     static boolean sHasCeFeature;
 
     // Final after first constructor, except for
@@ -484,7 +487,7 @@
      * A callback to be invoked when the system successfully delivers your {@link NdefMessage}
      * to another device.
      * @see #setOnNdefPushCompleteCallback
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @deprecated this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
@@ -510,7 +513,7 @@
      * content currently visible to the user. Alternatively, you can call {@link
      * #setNdefPushMessage setNdefPushMessage()} if the {@link NdefMessage} always contains the
      * same data.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @deprecated this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
@@ -540,7 +543,7 @@
 
 
      /**
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @deprecated this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
@@ -616,7 +619,6 @@
             PackageManager pm;
             pm = context.getPackageManager();
             sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
-            sHasBeamFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM);
             sHasCeFeature =
                     pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
                     || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)
@@ -1114,35 +1116,17 @@
      * @param uris an array of Uri(s) to push over Android Beam
      * @param activity activity for which the Uri(s) will be pushed
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
+    @UnsupportedAppUsage
     public void setBeamPushUris(Uri[] uris, Activity activity) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
         }
-        if (activity == null) {
-            throw new NullPointerException("activity cannot be null");
-        }
-        if (uris != null) {
-            for (Uri uri : uris) {
-                if (uri == null) throw new NullPointerException("Uri not " +
-                        "allowed to be null");
-                String scheme = uri.getScheme();
-                if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
-                        !scheme.equalsIgnoreCase("content"))) {
-                    throw new IllegalArgumentException("URI needs to have " +
-                            "either scheme file or scheme content");
-                }
-            }
-        }
-        mNfcActivityManager.setNdefPushContentUri(activity, uris);
     }
 
     /**
@@ -1202,23 +1186,17 @@
      * @param callback callback, or null to disable
      * @param activity activity for which the Uri(s) will be pushed
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
+    @UnsupportedAppUsage
     public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
         }
-        if (activity == null) {
-            throw new NullPointerException("activity cannot be null");
-        }
-        mNfcActivityManager.setNdefPushContentUriCallback(activity, callback);
     }
 
     /**
@@ -1292,58 +1270,32 @@
      *        to only register one at a time, and to do so in that activity's
      *        {@link Activity#onCreate}
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
+    @UnsupportedAppUsage
     public void setNdefPushMessage(NdefMessage message, Activity activity,
             Activity ... activities) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
-        }
-        int targetSdkVersion = getSdkVersion();
-        try {
-            if (activity == null) {
-                throw new NullPointerException("activity cannot be null");
-            }
-            mNfcActivityManager.setNdefPushMessage(activity, message, 0);
-            for (Activity a : activities) {
-                if (a == null) {
-                    throw new NullPointerException("activities cannot contain null");
-                }
-                mNfcActivityManager.setNdefPushMessage(a, message, 0);
-            }
-        } catch (IllegalStateException e) {
-            if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
-                // Less strict on old applications - just log the error
-                Log.e(TAG, "Cannot call API with Activity that has already " +
-                        "been destroyed", e);
-            } else {
-                // Prevent new applications from making this mistake, re-throw
-                throw(e);
-            }
         }
     }
 
     /**
      * @hide
+     * @removed
      */
     @SystemApi
+    @UnsupportedAppUsage
     public void setNdefPushMessage(NdefMessage message, Activity activity, int flags) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
         }
-        if (activity == null) {
-            throw new NullPointerException("activity cannot be null");
-        }
-        mNfcActivityManager.setNdefPushMessage(activity, message, flags);
     }
 
     /**
@@ -1411,54 +1363,18 @@
      *        to only register one at a time, and to do so in that activity's
      *        {@link Activity#onCreate}
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
+    @UnsupportedAppUsage
     public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
             Activity ... activities) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
         }
-        int targetSdkVersion = getSdkVersion();
-        try {
-            if (activity == null) {
-                throw new NullPointerException("activity cannot be null");
-            }
-            mNfcActivityManager.setNdefPushMessageCallback(activity, callback, 0);
-            for (Activity a : activities) {
-                if (a == null) {
-                    throw new NullPointerException("activities cannot contain null");
-                }
-                mNfcActivityManager.setNdefPushMessageCallback(a, callback, 0);
-            }
-        } catch (IllegalStateException e) {
-            if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
-                // Less strict on old applications - just log the error
-                Log.e(TAG, "Cannot call API with Activity that has already " +
-                        "been destroyed", e);
-            } else {
-                // Prevent new applications from making this mistake, re-throw
-                throw(e);
-            }
-        }
-    }
-
-    /**
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
-            int flags) {
-        if (activity == null) {
-            throw new NullPointerException("activity cannot be null");
-        }
-        mNfcActivityManager.setNdefPushMessageCallback(activity, callback, flags);
     }
 
     /**
@@ -1498,41 +1414,17 @@
      *        to only register one at a time, and to do so in that activity's
      *        {@link Activity#onCreate}
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
+    @UnsupportedAppUsage
     public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
             Activity activity, Activity ... activities) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
-        }
-        int targetSdkVersion = getSdkVersion();
-        try {
-            if (activity == null) {
-                throw new NullPointerException("activity cannot be null");
-            }
-            mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callback);
-            for (Activity a : activities) {
-                if (a == null) {
-                    throw new NullPointerException("activities cannot contain null");
-                }
-                mNfcActivityManager.setOnNdefPushCompleteCallback(a, callback);
-            }
-        } catch (IllegalStateException e) {
-            if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
-                // Less strict on old applications - just log the error
-                Log.e(TAG, "Cannot call API with Activity that has already " +
-                        "been destroyed", e);
-            } else {
-                // Prevent new applications from making this mistake, re-throw
-                throw(e);
-            }
         }
     }
 
@@ -1715,46 +1607,18 @@
      * @param activity the current foreground Activity that has registered data to share
      * @return whether the Beam animation was successfully invoked
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
+    @UnsupportedAppUsage
     public boolean invokeBeam(Activity activity) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return false;
-            }
         }
-        if (activity == null) {
-            throw new NullPointerException("activity may not be null.");
-        }
-        enforceResumed(activity);
-        try {
-            sService.invokeBeam();
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "invokeBeam: NFC process has died.");
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public boolean invokeBeam(BeamShareData shareData) {
-        try {
-            Log.e(TAG, "invokeBeamInternal()");
-            sService.invokeBeamInternal(shareData);
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "invokeBeam: NFC process has died.");
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return false;
     }
 
     /**
@@ -1780,25 +1644,18 @@
      *
      * @param activity foreground activity
      * @param message a NDEF Message to push over NFC
-     * @throws IllegalStateException if the activity is not currently in the foreground
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated use {@link #setNdefPushMessage} instead
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
+     * @removed this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
      */
     @Deprecated
+    @UnsupportedAppUsage
     public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
         }
-        if (activity == null || message == null) {
-            throw new NullPointerException();
-        }
-        enforceResumed(activity);
-        mNfcActivityManager.setNdefPushMessage(activity, message, 0);
     }
 
     /**
@@ -1817,27 +1674,18 @@
      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
      *
      * @param activity the Foreground activity
-     * @throws IllegalStateException if the Activity has already been paused
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated use {@link #setNdefPushMessage} instead
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
+     * @removed this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
      */
     @Deprecated
+    @UnsupportedAppUsage
     public void disableForegroundNdefPush(Activity activity) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
         }
-        if (activity == null) {
-            throw new NullPointerException();
-        }
-        enforceResumed(activity);
-        mNfcActivityManager.setNdefPushMessage(activity, null, 0);
-        mNfcActivityManager.setNdefPushMessageCallback(activity, null, 0);
-        mNfcActivityManager.setOnNdefPushCompleteCallback(activity, null);
     }
 
     /**
@@ -1971,40 +1819,26 @@
      * Enable NDEF Push feature.
      * <p>This API is for the Settings application.
      * @hide
+     * @removed
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @UnsupportedAppUsage
     public boolean enableNdefPush() {
-        if (!sHasNfcFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.enableNdefPush();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return false;
     }
 
     /**
      * Disable NDEF Push feature.
      * <p>This API is for the Settings application.
      * @hide
+     * @removed
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @UnsupportedAppUsage
     public boolean disableNdefPush() {
-        synchronized (NfcAdapter.class) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-        try {
-            return sService.disableNdefPush();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return false;
     }
 
     /**
@@ -2030,26 +1864,18 @@
      * @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS
      * @return true if NDEF Push feature is enabled
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
-
+    @UnsupportedAppUsage
     public boolean isNdefPushEnabled() {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return false;
-            }
         }
-        try {
-            return sService.isNdefPushEnabled();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return false;
     }
 
     /**
@@ -2140,17 +1966,6 @@
     }
 
     /**
-     * @hide
-     */
-    public void setP2pModes(int initiatorModes, int targetModes) {
-        try {
-            sService.setP2pModes(initiatorModes, targetModes);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
-    }
-
-    /**
      * Registers a new NFC unlock handler with the NFC service.
      *
      * <p />NFC unlock handlers are intended to unlock the keyguard in the presence of a trusted
diff --git a/core/java/android/nfc/tech/NdefFormatable.java b/core/java/android/nfc/tech/NdefFormatable.java
index f19d3025..2240fe7 100644
--- a/core/java/android/nfc/tech/NdefFormatable.java
+++ b/core/java/android/nfc/tech/NdefFormatable.java
@@ -124,6 +124,9 @@
         try {
             int serviceHandle = mTag.getServiceHandle();
             INfcTag tagService = mTag.getTagService();
+            if (tagService == null) {
+                throw new IOException();
+            }
             int errorCode = tagService.formatNdef(serviceHandle, MifareClassic.KEY_DEFAULT);
             switch (errorCode) {
                 case ErrorCodes.SUCCESS:
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 735a068..244632a 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -806,12 +806,9 @@
          * PackageManager.setComponentEnabledSetting} will now throw an
          * IllegalArgumentException if the given component class name does not
          * exist in the application's manifest.
-         * <li> {@link android.nfc.NfcAdapter#setNdefPushMessage
-         * NfcAdapter.setNdefPushMessage},
-         * {@link android.nfc.NfcAdapter#setNdefPushMessageCallback
-         * NfcAdapter.setNdefPushMessageCallback} and
-         * {@link android.nfc.NfcAdapter#setOnNdefPushCompleteCallback
-         * NfcAdapter.setOnNdefPushCompleteCallback} will throw
+         * <li> {@code NfcAdapter.setNdefPushMessage},
+         * {@code NfcAdapter.setNdefPushMessageCallback} and
+         * {@code NfcAdapter.setOnNdefPushCompleteCallback} will throw
          * IllegalStateException if called after the Activity has been destroyed.
          * <li> Accessibility services must require the new
          * {@link android.Manifest.permission#BIND_ACCESSIBILITY_SERVICE} permission or
diff --git a/core/java/android/os/PermissionEnforcer.java b/core/java/android/os/PermissionEnforcer.java
index 221e89a..310ceb3 100644
--- a/core/java/android/os/PermissionEnforcer.java
+++ b/core/java/android/os/PermissionEnforcer.java
@@ -18,9 +18,11 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemService;
+import android.app.AppOpsManager;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.content.PermissionChecker;
+import android.content.pm.PackageManager;
 import android.permission.PermissionCheckerManager;
 
 /**
@@ -40,6 +42,7 @@
 public class PermissionEnforcer {
 
     private final Context mContext;
+    private static final String ACCESS_DENIED = "Access denied, requires: ";
 
     /** Protected constructor. Allows subclasses to instantiate an object
      *  without using a Context.
@@ -59,11 +62,42 @@
             mContext, permission, PermissionChecker.PID_UNKNOWN, source, "" /* message */);
     }
 
+    @SuppressWarnings("AndroidFrameworkClientSidePermissionCheck")
+    @PermissionCheckerManager.PermissionResult
+    protected int checkPermission(@NonNull String permission, int pid, int uid) {
+        if (mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) {
+            return PermissionCheckerManager.PERMISSION_GRANTED;
+        }
+        return PermissionCheckerManager.PERMISSION_HARD_DENIED;
+    }
+
+    private boolean anyAppOps(@NonNull String[] permissions) {
+        for (String permission : permissions) {
+            if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public void enforcePermission(@NonNull String permission, @NonNull
             AttributionSource source) throws SecurityException {
         int result = checkPermission(permission, source);
         if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Access denied, requires: " + permission);
+            throw new SecurityException(ACCESS_DENIED + permission);
+        }
+    }
+
+    public void enforcePermission(@NonNull String permission, int pid, int uid)
+            throws SecurityException {
+        if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+            AttributionSource source = new AttributionSource(uid, null, null);
+            enforcePermission(permission, source);
+            return;
+        }
+        int result = checkPermission(permission, pid, uid);
+        if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+            throw new SecurityException(ACCESS_DENIED + permission);
         }
     }
 
@@ -72,7 +106,23 @@
         for (String permission : permissions) {
             int result = checkPermission(permission, source);
             if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Access denied, requires: allOf={"
+                throw new SecurityException(ACCESS_DENIED + "allOf={"
+                        + String.join(", ", permissions) + "}");
+            }
+        }
+    }
+
+    public void enforcePermissionAllOf(@NonNull String[] permissions,
+            int pid, int uid) throws SecurityException {
+        if (anyAppOps(permissions)) {
+            AttributionSource source = new AttributionSource(uid, null, null);
+            enforcePermissionAllOf(permissions, source);
+            return;
+        }
+        for (String permission : permissions) {
+            int result = checkPermission(permission, pid, uid);
+            if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+                throw new SecurityException(ACCESS_DENIED + "allOf={"
                         + String.join(", ", permissions) + "}");
             }
         }
@@ -86,7 +136,24 @@
                 return;
             }
         }
-        throw new SecurityException("Access denied, requires: anyOf={"
+        throw new SecurityException(ACCESS_DENIED + "anyOf={"
+                + String.join(", ", permissions) + "}");
+    }
+
+    public void enforcePermissionAnyOf(@NonNull String[] permissions,
+            int pid, int uid) throws SecurityException {
+        if (anyAppOps(permissions)) {
+            AttributionSource source = new AttributionSource(uid, null, null);
+            enforcePermissionAnyOf(permissions, source);
+            return;
+        }
+        for (String permission : permissions) {
+            int result = checkPermission(permission, pid, uid);
+            if (result == PermissionCheckerManager.PERMISSION_GRANTED) {
+                return;
+            }
+        }
+        throw new SecurityException(ACCESS_DENIED + "anyOf={"
                 + String.join(", ", permissions) + "}");
     }
 
diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java
index 878e141..970f419 100644
--- a/core/java/android/os/RemoteException.java
+++ b/core/java/android/os/RemoteException.java
@@ -21,6 +21,12 @@
 
 /**
  * Parent exception for all Binder remote-invocation errors
+ *
+ * Note: not all exceptions from binder services will be subclasses of this.
+ *   For instance, RuntimeException and several subclasses of it may be
+ *   thrown as well as OutOfMemoryException.
+ *
+ * One common subclass is {@link DeadObjectException}.
  */
 public class RemoteException extends AndroidException {
     public RemoteException() {
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 5aa4e27..7cdaecd 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -202,6 +202,13 @@
     public static final String ACTION_NOTIFY_IF_IN_USE =
             "android.os.image.action.NOTIFY_IF_IN_USE";
 
+    /**
+     * Intent action: hide notifications about the status of {@code DynamicSystem}.
+     * @hide
+     */
+    public static final String ACTION_HIDE_NOTIFICATION =
+            "android.os.image.action.HIDE_NOTIFICATION";
+
     /*
      * Intent Keys
      */
@@ -217,6 +224,28 @@
      */
     public static final String KEY_USERDATA_SIZE = "KEY_USERDATA_SIZE";
 
+    /**
+     * Intent key: Whether to enable DynamicSystem immediately after installation is done.
+     *             Note this will reboot the device automatically.
+     * @hide
+     */
+    public static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
+
+    /**
+     * Intent key: Whether to leave DynamicSystem on device reboot.
+     *             False indicates a sticky mode where device stays in DynamicSystem across reboots.
+     * @hide
+     */
+    public static final String KEY_ONE_SHOT = "KEY_ONE_SHOT";
+
+    /**
+     * Intent key: Whether to use default strings when showing the dialog that prompts
+     *             user for device credentials.
+     *             False indicates using the custom strings provided by {@code DynamicSystem}.
+     * @hide
+     */
+    public static final String KEY_KEYGUARD_USE_DEFAULT_STRINGS =
+            "KEY_KEYGUARD_USE_DEFAULT_STRINGS";
 
     private static class IncomingHandler extends Handler {
         private final WeakReference<DynamicSystemClient> mWeakClient;
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index d34b45b..4603e43f 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,18 +1,19 @@
 # Bug component: 137825
 
-evanseverson@google.com
-evanxinchen@google.com
 ashfall@google.com
-guojing@google.com
+augale@google.com
+evanseverson@google.com
+fayey@google.com
 jaysullivan@google.com
+joecastro@google.com
 kvakil@google.com
 mrulhania@google.com
 narayan@google.com
 ntmyren@google.com
 olekarg@google.com
 pyuli@google.com
-raphk@google.com
 rmacgregor@google.com
 sergeynv@google.com
 theianchen@google.com
+yutingfang@google.com
 zhanghai@google.com
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 3c1b4ba..0012572 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -8410,7 +8410,7 @@
             extras.putString(KEY_ACCOUNT_NAME, accountName);
             extras.putString(KEY_ACCOUNT_TYPE, accountType);
 
-            contentResolver.call(ContactsContract.AUTHORITY_URI,
+            nullSafeCall(contentResolver, ContactsContract.AUTHORITY_URI,
                     ContactsContract.SimContacts.ADD_SIM_ACCOUNT_METHOD,
                     null, extras);
         }
@@ -8433,7 +8433,7 @@
             Bundle extras = new Bundle();
             extras.putInt(KEY_SIM_SLOT_INDEX, simSlotIndex);
 
-            contentResolver.call(ContactsContract.AUTHORITY_URI,
+            nullSafeCall(contentResolver, ContactsContract.AUTHORITY_URI,
                     ContactsContract.SimContacts.REMOVE_SIM_ACCOUNT_METHOD,
                     null, extras);
         }
@@ -8445,7 +8445,7 @@
          */
         public static @NonNull List<SimAccount> getSimAccounts(
                 @NonNull ContentResolver contentResolver) {
-            Bundle response = contentResolver.call(ContactsContract.AUTHORITY_URI,
+            Bundle response = nullSafeCall(contentResolver, ContactsContract.AUTHORITY_URI,
                     ContactsContract.SimContacts.QUERY_SIM_ACCOUNTS_METHOD,
                     null, null);
             List<SimAccount> result = response.getParcelableArrayList(KEY_SIM_ACCOUNTS);
@@ -9064,7 +9064,8 @@
          * @param contactId the id of the contact to undemote.
          */
         public static void undemote(ContentResolver contentResolver, long contactId) {
-            contentResolver.call(ContactsContract.AUTHORITY_URI, PinnedPositions.UNDEMOTE_METHOD,
+            nullSafeCall(contentResolver, ContactsContract.AUTHORITY_URI,
+                    PinnedPositions.UNDEMOTE_METHOD,
                     String.valueOf(contactId), null);
         }
 
@@ -10276,4 +10277,13 @@
         public static final String CONTENT_ITEM_TYPE =
                 "vnd.android.cursor.item/contact_metadata_sync_state";
     }
+
+    private static Bundle nullSafeCall(@NonNull ContentResolver resolver, @NonNull Uri uri,
+            @NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
+        try (ContentProviderClient client = resolver.acquireContentProviderClient(uri)) {
+            return client.call(method, arg, extras);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 85e3aee..fe0c1dd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1596,7 +1596,6 @@
      * Input: Nothing.
      * <p>
      * Output: Nothing
-     * @see android.nfc.NfcAdapter#isNdefPushEnabled()
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_NFCSHARING_SETTINGS =
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 3dc805e..bb76b469 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4842,6 +4842,14 @@
          */
         public static final String COLUMN_USER_HANDLE = "user_handle";
 
+        /**
+         * TelephonyProvider column name for satellite enabled.
+         * By default, it's disabled.
+         *
+         * @hide
+         */
+        public static final String COLUMN_SATELLITE_ENABLED = "satellite_enabled";
+
         /** All columns in {@link SimInfo} table. */
         private static final List<String> ALL_COLUMNS = List.of(
                 COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
@@ -4910,7 +4918,8 @@
                 COLUMN_PORT_INDEX,
                 COLUMN_USAGE_SETTING,
                 COLUMN_TP_MESSAGE_REF,
-                COLUMN_USER_HANDLE
+                COLUMN_USER_HANDLE,
+                COLUMN_SATELLITE_ENABLED
         );
 
         /**
diff --git a/core/java/android/security/net/config/ManifestConfigSource.java b/core/java/android/security/net/config/ManifestConfigSource.java
index b885e72..0e20997 100644
--- a/core/java/android/security/net/config/ManifestConfigSource.java
+++ b/core/java/android/security/net/config/ManifestConfigSource.java
@@ -25,7 +25,7 @@
 
 /** @hide */
 public class ManifestConfigSource implements ConfigSource {
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
     private static final String LOG_TAG = "NetworkSecurityConfig";
 
     private final Object mLock = new Object();
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index d48d566..e6dad27 100755
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -24,12 +24,12 @@
 import android.content.Context;
 import android.icu.text.DateFormatSymbols;
 import android.icu.text.DateTimePatternGenerator;
+import android.icu.util.ULocale;
 import android.os.Build;
 import android.provider.Settings;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.SpannedString;
-import android.text.TextUtils;
 
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
@@ -265,11 +265,13 @@
      * @return a string pattern suitable for use with {@link java.text.SimpleDateFormat}.
      */
     public static String getBestDateTimePattern(Locale locale, String skeleton) {
-        DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(locale);
+        ULocale uLocale = ULocale.forLocale(locale);
+        DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(uLocale);
         boolean allowDuplicateFields = !CompatChanges.isChangeEnabled(
                 DISALLOW_DUPLICATE_FIELD_IN_SKELETON);
-        return dtpg.getBestPattern(skeleton, DateTimePatternGenerator.MATCH_NO_OPTIONS,
+        String pattern = dtpg.getBestPattern(skeleton, DateTimePatternGenerator.MATCH_NO_OPTIONS,
                 allowDuplicateFields);
+        return getCompatibleEnglishPattern(uLocale, pattern);
     }
 
     /**
@@ -303,10 +305,11 @@
      */
     @UnsupportedAppUsage
     public static String getTimeFormatString(Context context, int userHandle) {
-        DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(
-                context.getResources().getConfiguration().locale);
-        return is24HourFormat(context, userHandle) ? dtpg.getBestPattern("Hm")
+        ULocale uLocale = ULocale.forLocale(context.getResources().getConfiguration().locale);
+        DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(uLocale);
+        String pattern = is24HourFormat(context, userHandle) ? dtpg.getBestPattern("Hm")
             : dtpg.getBestPattern("hm");
+        return getCompatibleEnglishPattern(uLocale, pattern);
     }
 
     /**
@@ -713,4 +716,21 @@
     public static DateFormatSymbols getIcuDateFormatSymbols(Locale locale) {
         return new DateFormatSymbols(android.icu.util.GregorianCalendar.class, locale);
     }
+
+    /**
+     * See http://b/266731719. It mirrors the implementation in
+     * {@link libcore.icu.SimpleDateFormatData.DateTimeFormatStringGenerator#postProcessPattern}
+     */
+    private static String getCompatibleEnglishPattern(ULocale locale, String pattern) {
+        if (pattern == null || locale == null || !"en".equals(locale.getLanguage())) {
+            return pattern;
+        }
+
+        String region = locale.getCountry();
+        if (region != null && !region.isEmpty() && !"US".equals(region)) {
+            return pattern;
+        }
+
+        return pattern.replace('\u202f', ' ');
+    }
 }
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 3a74b2e..c7f7456 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -138,8 +138,12 @@
         mMessageQueue = null;
     }
 
-    static final class VsyncEventData {
-
+    /**
+     * Class to capture all inputs required for syncing events data.
+     *
+     * @hide
+     */
+    public static final class VsyncEventData {
         static final FrameTimeline[] INVALID_FRAME_TIMELINES =
                 {new FrameTimeline(FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE)};
 
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index 6d64022..a3c278f 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -15,6 +15,10 @@
 # Autofill
 per-file ViewStructure.java = file:/core/java/android/service/autofill/OWNERS
 
+# Choreographer
+per-file Choreographer.java = file:platform/frameworks/native:/services/surfaceflinger/OWNERS
+per-file DisplayEventReceiver.java = file:platform/frameworks/native:/services/surfaceflinger/OWNERS
+
 # Display
 per-file Display*.java = file:/services/core/java/com/android/server/display/OWNERS
 per-file Display*.aidl = file:/services/core/java/com/android/server/display/OWNERS
@@ -56,6 +60,9 @@
 per-file SurfaceHolder.java = file:/graphics/java/android/graphics/OWNERS
 per-file SurfaceHolder.java = file:/services/core/java/com/android/server/wm/OWNERS
 
+# Text
+per-file HandwritingInitiator.java = file:/core/java/android/text/OWNERS
+
 # View
 per-file View.java = file:/services/accessibility/OWNERS
 per-file View.java = file:/core/java/android/service/autofill/OWNERS
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 3df09c2..1f0e95e 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -49,15 +49,18 @@
 import java.util.Locale;
 
 /**
- * This widget display an analogic clock with two hands for hours and
- * minutes.
+ * This widget displays an analogic clock with two hands for hours and minutes.
  *
  * @attr ref android.R.styleable#AnalogClock_dial
  * @attr ref android.R.styleable#AnalogClock_hand_hour
  * @attr ref android.R.styleable#AnalogClock_hand_minute
  * @attr ref android.R.styleable#AnalogClock_hand_second
  * @attr ref android.R.styleable#AnalogClock_timeZone
- * @deprecated This widget is no longer supported.
+ * @deprecated This widget is no longer supported; except for
+ * {@link android.widget.RemoteViews} use cases like
+ * <a href="https://developer.android.com/develop/ui/views/appwidgets/overview">
+ * app widgets</a>.
+ *
  */
 @RemoteView
 @Deprecated
diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS
index a1d571f..52f18fb 100644
--- a/core/java/com/android/internal/app/OWNERS
+++ b/core/java/com/android/internal/app/OWNERS
@@ -1,15 +1,16 @@
 per-file *AppOp* = file:/core/java/android/permission/OWNERS
 per-file UnlaunchableAppActivity.java = file:/core/java/android/app/admin/WorkProfile_OWNERS
 per-file IntentForwarderActivity.java = file:/core/java/android/app/admin/WorkProfile_OWNERS
-per-file *Resolver* = file:/packages/SystemUI/OWNERS
-per-file *Chooser* = file:/packages/SystemUI/OWNERS
-per-file SimpleIconFactory.java = file:/packages/SystemUI/OWNERS
-per-file AbstractMultiProfilePagerAdapter.java = file:/packages/SystemUI/OWNERS
-per-file *EmptyStateProvider.java = file:/packages/SystemUI/OWNERS
 per-file NetInitiatedActivity.java = file:/location/java/android/location/OWNERS
 per-file *BatteryStats* = file:/BATTERY_STATS_OWNERS
 per-file *SoundTrigger* = file:/media/java/android/media/soundtrigger/OWNERS
 
+# Chooser and Resolver.
+per-file *Chooser* = file:chooser/OWNERS
+per-file *Resolver* = file:chooser/OWNERS
+per-file SimpleIconFactory.java = file:chooser/OWNERS
+per-file AbstractMultiProfilePagerAdapter.java = file:chooser/OWNERS
+per-file *EmptyStateProvider.java = file:chooser/OWNERS
 
 # Voice Interaction
 per-file *Assist* = file:/core/java/android/service/voice/OWNERS
diff --git a/core/java/com/android/internal/app/chooser/OWNERS b/core/java/com/android/internal/app/chooser/OWNERS
index a6f1632..0844cfa 100644
--- a/core/java/com/android/internal/app/chooser/OWNERS
+++ b/core/java/com/android/internal/app/chooser/OWNERS
@@ -1 +1,3 @@
-file:/packages/SystemUI/OWNERS
\ No newline at end of file
+# Bug component: 324112
+
+include platform/packages/modules/IntentResolver:/OWNERS
diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java
index 2f3b662..2fe784a 100644
--- a/core/java/com/android/internal/expresslog/Histogram.java
+++ b/core/java/com/android/internal/expresslog/Histogram.java
@@ -16,10 +16,14 @@
 
 package com.android.internal.expresslog;
 
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 
 import com.android.internal.util.FrameworkStatsLog;
 
+import java.util.Arrays;
+
 /** Histogram encapsulates StatsD write API calls */
 public final class Histogram {
 
@@ -28,7 +32,8 @@
 
     /**
      * Creates Histogram metric logging wrapper
-     * @param metricId to log, logging will be no-op if metricId is not defined in the TeX catalog
+     *
+     * @param metricId   to log, logging will be no-op if metricId is not defined in the TeX catalog
      * @param binOptions to calculate bin index for samples
      * @hide
      */
@@ -39,6 +44,7 @@
 
     /**
      * Logs increment sample count for automatically calculated bin
+     *
      * @param sample value
      * @hide
      */
@@ -48,10 +54,24 @@
                 /*count*/ 1, binIndex);
     }
 
+    /**
+     * Logs increment sample count for automatically calculated bin
+     *
+     * @param uid used as a dimension for the count metric
+     * @param sample value
+     * @hide
+     */
+    public void logSampleWithUid(int uid, float sample) {
+        final int binIndex = mBinOptions.getBinForSample(sample);
+        FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED,
+                mMetricIdHash, /*count*/ 1, binIndex, uid);
+    }
+
     /** Used by Histogram to map data sample to corresponding bin */
     public interface BinOptions {
         /**
          * Returns bins count to be used by a histogram
+         *
          * @return bins count used to initialize Options, including overflow & underflow bins
          * @hide
          */
@@ -61,6 +81,7 @@
          * Returns bin index for the input sample value
          * index == 0 stands for underflow
          * index == getBinsCount() - 1 stands for overflow
+         *
          * @return zero based index
          * @hide
          */
@@ -76,17 +97,19 @@
         private final float mBinSize;
 
         /**
-         * Creates otpions for uniform (linear) sized bins
-         * @param binCount amount of histogram bins. 2 bin indexes will be calculated
-         *                 automatically to represent undeflow & overflow bins
-         * @param minValue is included in the first bin, values less than minValue
-         *                 go to underflow bin
+         * Creates options for uniform (linear) sized bins
+         *
+         * @param binCount          amount of histogram bins. 2 bin indexes will be calculated
+         *                          automatically to represent underflow & overflow bins
+         * @param minValue          is included in the first bin, values less than minValue
+         *                          go to underflow bin
          * @param exclusiveMaxValue is included in the overflow bucket. For accurate
-                                    measure up to kMax, then exclusiveMaxValue
+         *                          measure up to kMax, then exclusiveMaxValue
          *                          should be set to kMax + 1
          * @hide
          */
-        public UniformOptions(int binCount, float minValue, float exclusiveMaxValue) {
+        public UniformOptions(@IntRange(from = 1) int binCount, float minValue,
+                float exclusiveMaxValue) {
             if (binCount < 1) {
                 throw new IllegalArgumentException("Bin count should be positive number");
             }
@@ -99,7 +122,7 @@
             mExclusiveMaxValue = exclusiveMaxValue;
             mBinSize = (mExclusiveMaxValue - minValue) / binCount;
 
-            // Implicitly add 2 for the extra undeflow & overflow bins
+            // Implicitly add 2 for the extra underflow & overflow bins
             mBinCount = binCount + 2;
         }
 
@@ -120,4 +143,92 @@
             return (int) ((sample - mMinValue) / mBinSize + 1);
         }
     }
+
+    /** Used by Histogram to map data sample to corresponding bin for scaled bins */
+    public static final class ScaledRangeOptions implements BinOptions {
+        // store minimum value per bin
+        final long[] mBins;
+
+        /**
+         * Creates options for scaled range bins
+         *
+         * @param binCount      amount of histogram bins. 2 bin indexes will be calculated
+         *                      automatically to represent underflow & overflow bins
+         * @param minValue      is included in the first bin, values less than minValue
+         *                      go to underflow bin
+         * @param firstBinWidth used to represent first bin width and as a reference to calculate
+         *                      width for consecutive bins
+         * @param scaleFactor   used to calculate width for consecutive bins
+         * @hide
+         */
+        public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue,
+                @FloatRange(from = 1.f) float firstBinWidth,
+                @FloatRange(from = 1.f) float scaleFactor) {
+            if (binCount < 1) {
+                throw new IllegalArgumentException("Bin count should be positive number");
+            }
+
+            if (firstBinWidth < 1.f) {
+                throw new IllegalArgumentException(
+                        "First bin width invalid (should be 1.f at minimum)");
+            }
+
+            if (scaleFactor < 1.f) {
+                throw new IllegalArgumentException(
+                        "Scaled factor invalid (should be 1.f at minimum)");
+            }
+
+            // precalculating bins ranges (no need to create a bin for underflow reference value)
+            mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor);
+        }
+
+        @Override
+        public int getBinsCount() {
+            return mBins.length + 1;
+        }
+
+        @Override
+        public int getBinForSample(float sample) {
+            if (sample < mBins[0]) {
+                // goes to underflow
+                return 0;
+            } else if (sample >= mBins[mBins.length - 1]) {
+                // goes to overflow
+                return mBins.length;
+            }
+
+            return lower_bound(mBins, (long) sample) + 1;
+        }
+
+        // To find lower bound using binary search implementation of Arrays utility class
+        private static int lower_bound(long[] array, long sample) {
+            int index = Arrays.binarySearch(array, sample);
+            // If key is not present in the array
+            if (index < 0) {
+                // Index specify the position of the key when inserted in the sorted array
+                // so the element currently present at this position will be the lower bound
+                return Math.abs(index) - 2;
+            }
+            return index;
+        }
+
+        private static long[] initBins(int count, int minValue, float firstBinWidth,
+                float scaleFactor) {
+            long[] bins = new long[count];
+            bins[0] = minValue;
+            double lastWidth = firstBinWidth;
+            for (int i = 1; i < count; i++) {
+                // current bin minValue = previous bin width * scaleFactor
+                double currentBinMinValue = bins[i - 1] + lastWidth;
+                if (currentBinMinValue > Integer.MAX_VALUE) {
+                    throw new IllegalArgumentException(
+                        "Attempted to create a bucket larger than maxint");
+                }
+
+                bins[i] = (long) currentBinMinValue;
+                lastWidth *= scaleFactor;
+            }
+            return bins;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 05cad77..d8aeb51 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -1018,7 +1018,7 @@
      * Applies debugger system properties to the zygote arguments.
      *
      * For eng builds all apps are debuggable. On userdebug and user builds
-     * if persist.debuggable.dalvik.vm.jdwp.enabled is 1 all apps are
+     * if persist.debug.dalvik.vm.jdwp.enabled is 1 all apps are
      * debuggable. Otherwise, the debugger state is specified via the
      * "--enable-jdwp" flag in the spawn request.
      *
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index c99a27a..1e7a93c 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -5,6 +5,9 @@
 # Connectivity
 per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
 
+# Choreographer
+per-file android_view_DisplayEventReceiver* = file:platform/frameworks/native:/services/surfaceflinger/OWNERS
+
 # CPU
 per-file *Cpu* = file:/core/java/com/android/internal/os/CPU_OWNERS
 
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index cdb0580..56c2a864 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2382,36 +2382,31 @@
 
     if (jSurroundFormats == nullptr) {
         ALOGE("jSurroundFormats is NULL");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
     if (!env->IsInstanceOf(jSurroundFormats, gMapClass)) {
         ALOGE("getSurroundFormats not a map");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
 
     jint jStatus;
     unsigned int numSurroundFormats = 0;
-    audio_format_t *surroundFormats = nullptr;
-    bool *surroundFormatsEnabled = nullptr;
-    status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats,
-                                                      surroundFormatsEnabled);
+    status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, nullptr, nullptr);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status);
-        jStatus = nativeToJavaStatus(status);
-        goto exit;
+        return nativeToJavaStatus(status);
     }
     if (numSurroundFormats == 0) {
-        jStatus = (jint)AUDIO_JAVA_SUCCESS;
-        goto exit;
+        return static_cast<jint>(AUDIO_JAVA_SUCCESS);
     }
-    surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t));
-    surroundFormatsEnabled = (bool *)calloc(numSurroundFormats, sizeof(bool));
-    status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats,
-                                             surroundFormatsEnabled);
+    auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats);
+    auto surroundFormatsEnabled = std::make_unique<bool[]>(numSurroundFormats);
+    status = AudioSystem::getSurroundFormats(&numSurroundFormats, &surroundFormats[0],
+                                             &surroundFormatsEnabled[0]);
     jStatus = nativeToJavaStatus(status);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status);
-        goto exit;
+        return jStatus;
     }
     for (size_t i = 0; i < numSurroundFormats; i++) {
         int audioFormat = audioFormatFromNative(surroundFormats[i]);
@@ -2427,9 +2422,6 @@
         env->DeleteLocalRef(enabled);
     }
 
-exit:
-    free(surroundFormats);
-    free(surroundFormatsEnabled);
     return jStatus;
 }
 
@@ -2439,31 +2431,28 @@
 
     if (jSurroundFormats == nullptr) {
         ALOGE("jSurroundFormats is NULL");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
     if (!env->IsInstanceOf(jSurroundFormats, gArrayListClass)) {
         ALOGE("jSurroundFormats not an arraylist");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
     }
     jint jStatus;
     unsigned int numSurroundFormats = 0;
-    audio_format_t *surroundFormats = nullptr;
-    status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats);
+    status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, nullptr);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status);
-        jStatus = nativeToJavaStatus(status);
-        goto exit;
+        return nativeToJavaStatus(status);
     }
     if (numSurroundFormats == 0) {
-        jStatus = (jint)AUDIO_JAVA_SUCCESS;
-        goto exit;
+        return static_cast<jint>(AUDIO_JAVA_SUCCESS);
     }
-    surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t));
-    status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats);
+    auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats);
+    status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, &surroundFormats[0]);
     jStatus = nativeToJavaStatus(status);
     if (status != NO_ERROR) {
         ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status);
-        goto exit;
+        return jStatus;
     }
     for (size_t i = 0; i < numSurroundFormats; i++) {
         int audioFormat = audioFormatFromNative(surroundFormats[i]);
@@ -2477,8 +2466,6 @@
         env->DeleteLocalRef(surroundFormat);
     }
 
-exit:
-    free(surroundFormats);
     return jStatus;
 }
 
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index a7c7d0b..b24dc8a 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -413,9 +413,13 @@
 
         if (env->ExceptionCheck()) {
             ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred());
-            binder_report_exception(env, excep.get(),
-                                    "*** Uncaught remote exception!  "
-                                    "(Exceptions are not yet supported across processes.)");
+
+            auto state = IPCThreadState::self();
+            String8 msg;
+            msg.appendFormat("*** Uncaught remote exception! Exceptions are not yet supported "
+                             "across processes. Client PID %d UID %d.",
+                             state->getCallingPid(), state->getCallingUid());
+            binder_report_exception(env, excep.get(), msg.c_str());
             res = JNI_FALSE;
         }
 
@@ -431,6 +435,7 @@
             ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred());
             binder_report_exception(env, excep.get(),
                                     "*** Uncaught exception in onBinderStrictModePolicyChange");
+            // TODO: should turn this to fatal?
         }
 
         // Need to always call through the native implementation of
diff --git a/core/proto/android/nfc/nfc_service.proto b/core/proto/android/nfc/nfc_service.proto
index 2df1d5d..1dcd5cc 100644
--- a/core/proto/android/nfc/nfc_service.proto
+++ b/core/proto/android/nfc/nfc_service.proto
@@ -60,7 +60,7 @@
     optional bool secure_nfc_capable = 13;
     optional bool vr_mode_enabled = 14;
     optional DiscoveryParamsProto discovery_params = 15;
-    optional P2pLinkManagerProto p2p_link_manager = 16;
+    reserved 16;
     optional com.android.nfc.cardemulation.CardEmulationManagerProto card_emulation_manager = 17;
     optional NfcDispatcherProto nfc_dispatcher = 18;
     optional string native_crash_logs = 19 [(.android.privacy).dest = DEST_EXPLICIT];
@@ -77,38 +77,6 @@
     optional bool enable_p2p = 5;
 }
 
-// Debugging information for com.android.nfc.P2pLinkManager
-message P2pLinkManagerProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-
-    enum LinkState {
-        LINK_STATE_UNKNOWN = 0;
-        LINK_STATE_DOWN = 1;
-        LINK_STATE_DEBOUNCE = 2;
-        LINK_STATE_UP = 3;
-    }
-
-    enum SendState {
-        SEND_STATE_UNKNOWN = 0;
-        SEND_STATE_NOTHING_TO_SEND = 1;
-        SEND_STATE_NEED_CONFIRMATION = 2;
-        SEND_STATE_SENDING = 3;
-        SEND_STATE_COMPLETE = 4;
-        SEND_STATE_CANCELED = 5;
-    }
-
-    optional int32 default_miu = 1;
-    optional int32 default_rw_size = 2;
-    optional LinkState link_state = 3;
-    optional SendState send_state = 4;
-    optional int32 send_flags = 5;
-    optional bool send_enabled = 6;
-    optional bool receive_enabled = 7;
-    optional string callback_ndef = 8 [(.android.privacy).dest = DEST_EXPLICIT];
-    optional .android.nfc.NdefMessageProto message_to_send = 9;
-    repeated string uris_to_send = 10 [(.android.privacy).dest = DEST_EXPLICIT];
-}
-
 // Debugging information for com.android.nfc.NfcDispatcher
 message NfcDispatcherProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 7205dd8..c596c54 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -1008,6 +1008,7 @@
 
             optional int32 uid = 1;
             repeated .android.app.ApplicationExitInfoProto app_exit_info = 2;
+            repeated .android.app.ApplicationExitInfoProto app_recoverable_crash = 3;
         }
         repeated User users = 2;
     }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a5e9617..277bdf3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1334,8 +1334,7 @@
         android:permissionFlags="hardRestricted"
         android:protectionLevel="dangerous" />
 
-    <!-- Allows an application to write (but not read) the user's
-         call log data.
+    <!-- Allows an application to write and read the user's call log data.
          <p class="note"><strong>Note:</strong> If your app uses the
          {@link #WRITE_CONTACTS} permission and <em>both</em> your <a
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 94cf1b2..504492b 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -122,4 +122,10 @@
          the performance, but sync mode reduces the chance of database/cache out-of-sync. -->
     <bool name="config_subscription_database_async_update">true</bool>
     <java-symbol type="bool" name="config_subscription_database_async_update" />
+
+    <!--  Whether to enable getSubscriptionUserHandle() api.
+          If the value is true, return user handle associated with the subscription.
+          If the value is set to false, return null. -->
+    <bool name="config_enable_get_subscription_user_handle">true</bool>
+    <java-symbol type="bool" name="config_enable_get_subscription_user_handle" />
 </resources>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 48c9df0..035eaee 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -84,8 +84,8 @@
         ":BstatsTestApp",
         ":BinderDeathRecipientHelperApp1",
         ":BinderDeathRecipientHelperApp2",
+        ":com.android.cts.helpers.aosp",
     ],
-    required: ["com.android.cts.helpers.aosp"],
 }
 
 // Rules to copy all the test apks to the intermediate raw resource directory
diff --git a/core/tests/coretests/src/android/app/OWNERS b/core/tests/coretests/src/android/app/OWNERS
index b3f3993..8d9461d 100644
--- a/core/tests/coretests/src/android/app/OWNERS
+++ b/core/tests/coretests/src/android/app/OWNERS
@@ -4,3 +4,6 @@
 per-file *Notification* = file:/packages/SystemUI/OWNERS
 per-file *Zen* = file:/packages/SystemUI/OWNERS
 per-file *StatusBar* = file:/packages/SystemUI/OWNERS
+
+# A11Y and related
+per-file *UiAutomation* = file:/services/accessibility/OWNERS
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index 8459330..212cc44 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -156,8 +156,8 @@
     @DisableCompatChanges({DateFormat.DISALLOW_DUPLICATE_FIELD_IN_SKELETON})
     public void testGetBestDateTimePattern_enableDuplicateField() {
         // en-US uses 12-hour format by default.
-        assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
-        assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
+        assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
+        assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
     }
 
     private static void assertIllegalArgumentException(Locale l, String skeleton) {
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 39ed82ef..381c051 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -139,16 +139,16 @@
                 fixedTime, java.text.DateFormat.SHORT, java.text.DateFormat.FULL));
 
         final long hourDuration = 2 * 60 * 60 * 1000;
-        assertEquals("5:30:15\u202fAM Greenwich Mean Time", DateUtils.formatSameDayTime(
+        assertEquals("5:30:15 AM Greenwich Mean Time", DateUtils.formatSameDayTime(
                 fixedTime + hourDuration, fixedTime, java.text.DateFormat.FULL,
                 java.text.DateFormat.FULL));
-        assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+        assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
                 fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.DEFAULT));
-        assertEquals("5:30:15\u202fAM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+        assertEquals("5:30:15 AM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
                 fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.LONG));
-        assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+        assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
                 fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.MEDIUM));
-        assertEquals("5:30\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+        assertEquals("5:30 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
                 fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.SHORT));
     }
 
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
new file mode 100644
index 0000000..ee62d75
--- /dev/null
+++ b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 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.expresslog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class ScaledRangeOptionsTest {
+    private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName();
+
+    @Test
+    public void testGetBinsCount() {
+        Histogram.ScaledRangeOptions options1 = new Histogram.ScaledRangeOptions(1, 100, 100, 2);
+        assertEquals(3, options1.getBinsCount());
+
+        Histogram.ScaledRangeOptions options10 = new Histogram.ScaledRangeOptions(10, 100, 100, 2);
+        assertEquals(12, options10.getBinsCount());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructZeroBinsCount() {
+        new Histogram.ScaledRangeOptions(0, 100, 100, 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructNegativeBinsCount() {
+        new Histogram.ScaledRangeOptions(-1, 100, 100, 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructNegativeFirstBinWidth() {
+        new Histogram.ScaledRangeOptions(10, 100, -100, 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructTooSmallFirstBinWidth() {
+        new Histogram.ScaledRangeOptions(10, 100, 0.5f, 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructNegativeScaleFactor() {
+        new Histogram.ScaledRangeOptions(10, 100, 100, -2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructTooSmallScaleFactor() {
+        new Histogram.ScaledRangeOptions(10, 100, 100, 0.5f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructTooBigScaleFactor() {
+        new Histogram.ScaledRangeOptions(10, 100, 100, 500.f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructTooBigBinRange() {
+        new Histogram.ScaledRangeOptions(100, 100, 100, 10.f);
+    }
+
+    @Test
+    public void testBinIndexForRangeEqual1() {
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 1, 1);
+        assertEquals(12, options.getBinsCount());
+
+        assertEquals(11, options.getBinForSample(11));
+
+        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+            assertEquals(i, options.getBinForSample(i));
+        }
+    }
+
+    @Test
+    public void testBinIndexForRangeEqual2() {
+        // this should produce bin otpions similar to linear histogram with bin width 2
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 2, 1);
+        assertEquals(12, options.getBinsCount());
+
+        for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+            assertEquals(i, options.getBinForSample(i * 2));
+            assertEquals(i, options.getBinForSample(i * 2 - 1));
+        }
+    }
+
+    @Test
+    public void testBinIndexForRangeEqual5() {
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(2, 0, 5, 1);
+        assertEquals(4, options.getBinsCount());
+        for (int i = 0; i < 2; i++) {
+            for (int sample = 0; sample < 5; sample++) {
+                assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
+            }
+        }
+    }
+
+    @Test
+    public void testBinIndexForRangeEqual10() {
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 10, 1);
+        assertEquals(0, options.getBinForSample(0));
+        assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
+        assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
+
+        final float binSize = (101 - 1) / 10f;
+        for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
+            assertEquals(i, options.getBinForSample(i * binSize));
+        }
+    }
+
+    @Test
+    public void testBinIndexForScaleFactor2() {
+        final int binsCount = 10;
+        final int minValue = 10;
+        final int firstBinWidth = 5;
+        final int scaledFactor = 2;
+
+        Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(
+                binsCount, minValue, firstBinWidth, scaledFactor);
+        assertEquals(binsCount + 2, options.getBinsCount());
+        long[] binCounts = new long[10];
+
+        // precalculate max valid value - start value for the overflow bin
+        int lastBinStartValue = minValue; //firstBinMin value
+        int lastBinWidth = firstBinWidth;
+        for (int binIdx = 2; binIdx <= binsCount + 1; binIdx++) {
+            lastBinStartValue = lastBinStartValue + lastBinWidth;
+            lastBinWidth *= scaledFactor;
+        }
+
+        // underflow bin
+        for (int i = 1; i < minValue; i++) {
+            assertEquals(0, options.getBinForSample(i));
+        }
+
+        for (int i = 10; i < lastBinStartValue; i++) {
+            assertTrue(options.getBinForSample(i) > 0);
+            assertTrue(options.getBinForSample(i) <= binsCount);
+            binCounts[options.getBinForSample(i) - 1]++;
+        }
+
+        // overflow bin
+        assertEquals(binsCount + 1, options.getBinForSample(lastBinStartValue));
+
+        for (int i = 1; i < binsCount; i++) {
+            assertEquals(binCounts[i], binCounts[i - 1] * 2L);
+        }
+    }
+}
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
index 9fa6d06..037dbb3 100644
--- a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
+++ b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
@@ -24,11 +24,11 @@
 import org.junit.runners.JUnit4;
 
 @RunWith(JUnit4.class)
+@SmallTest
 public class UniformOptionsTest {
     private static final String TAG = UniformOptionsTest.class.getSimpleName();
 
     @Test
-    @SmallTest
     public void testGetBinsCount() {
         Histogram.UniformOptions options1 = new Histogram.UniformOptions(1, 100, 1000);
         assertEquals(3, options1.getBinsCount());
@@ -38,25 +38,21 @@
     }
 
     @Test(expected = IllegalArgumentException.class)
-    @SmallTest
     public void testConstructZeroBinsCount() {
         new Histogram.UniformOptions(0, 100, 1000);
     }
 
     @Test(expected = IllegalArgumentException.class)
-    @SmallTest
     public void testConstructNegativeBinsCount() {
         new Histogram.UniformOptions(-1, 100, 1000);
     }
 
     @Test(expected = IllegalArgumentException.class)
-    @SmallTest
     public void testConstructMaxValueLessThanMinValue() {
         new Histogram.UniformOptions(10, 1000, 100);
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual1() {
         Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 11);
         for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
@@ -65,7 +61,6 @@
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual2() {
         Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 21);
         for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
@@ -75,7 +70,6 @@
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual5() {
         Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10);
         assertEquals(4, options.getBinsCount());
@@ -87,7 +81,6 @@
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual10() {
         Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101);
         assertEquals(0, options.getBinForSample(0));
@@ -101,7 +94,6 @@
     }
 
     @Test
-    @SmallTest
     public void testBinIndexForRangeEqual90() {
         final int binCount = 10;
         final int minValue = 100;
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
index d785c3c..f26b50e 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -21,10 +21,7 @@
 import android.content.Context;
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
-import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.security.GenerateRkpKey;
-import android.security.keymaster.KeymasterDefs;
 
 class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
 
@@ -125,18 +122,7 @@
             @NonNull String docType) throws AlreadyPersonalizedException,
             DocTypeNotSupportedException {
         try {
-            IWritableCredential wc;
-            wc = mStore.createCredential(credentialName, docType);
-            try {
-                GenerateRkpKey keyGen = new GenerateRkpKey(mContext);
-                // We don't know what the security level is for the backing keymint, so go ahead and
-                // poke the provisioner for both TEE and SB.
-                keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT);
-                keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_STRONGBOX);
-            } catch (RemoteException e) {
-                // Not really an error state. Does not apply at all if RKP is unsupported or
-                // disabled on a given device.
-            }
+            IWritableCredential wc = mStore.createCredential(credentialName, docType);
             return new CredstoreWritableIdentityCredential(mContext, credentialName, docType, wc);
         } catch (android.os.RemoteException e) {
             throw new RuntimeException("Unexpected RemoteException ", e);
diff --git a/keystore/java/android/security/GenerateRkpKey.java b/keystore/java/android/security/GenerateRkpKey.java
deleted file mode 100644
index 6981332..0000000
--- a/keystore/java/android/security/GenerateRkpKey.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * 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.security;
-
-import android.annotation.CheckResult;
-import android.annotation.IntDef;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-/**
- * GenerateKey is a helper class to handle interactions between Keystore and the RemoteProvisioner
- * app. There are two cases where Keystore should use this class.
- *
- * (1) : An app generates a new attested key pair, so Keystore calls notifyKeyGenerated to let the
- *       RemoteProvisioner app check if the state of the attestation key pool is getting low enough
- *       to warrant provisioning more attestation certificates early.
- *
- * (2) : An app attempts to generate a new key pair, but the keystore service discovers it is out of
- *       attestation key pairs and cannot provide one for the given application. Keystore can then
- *       make a blocking call on notifyEmpty to allow the RemoteProvisioner app to get another
- *       attestation certificate chain provisioned.
- *
- * In most cases, the proper usage of (1) should preclude the need for (2).
- *
- * @hide
- */
-public class GenerateRkpKey {
-    private static final String TAG = "GenerateRkpKey";
-
-    private static final int NOTIFY_EMPTY = 0;
-    private static final int NOTIFY_KEY_GENERATED = 1;
-    private static final int TIMEOUT_MS = 1000;
-
-    private IGenerateRkpKeyService mBinder;
-    private Context mContext;
-    private CountDownLatch mCountDownLatch;
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, value = {
-            IGenerateRkpKeyService.Status.OK,
-            IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY,
-            IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR,
-            IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED,
-            IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR,
-            IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR,
-            IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR,
-            IGenerateRkpKeyService.Status.INTERNAL_ERROR,
-    })
-    public @interface Status {
-    }
-
-    private ServiceConnection mConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            mBinder = IGenerateRkpKeyService.Stub.asInterface(service);
-            mCountDownLatch.countDown();
-        }
-
-        @Override public void onBindingDied(ComponentName className) {
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName className) {
-            mBinder = null;
-        }
-    };
-
-    /**
-     * Constructor which takes a Context object.
-     */
-    public GenerateRkpKey(Context context) {
-        mContext = context;
-    }
-
-    @Status
-    private int bindAndSendCommand(int command, int securityLevel) throws RemoteException {
-        Intent intent = new Intent(IGenerateRkpKeyService.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        int returnCode = IGenerateRkpKeyService.Status.OK;
-        if (comp == null) {
-            // On a system that does not use RKP, the RemoteProvisioner app won't be installed.
-            return returnCode;
-        }
-        intent.setComponent(comp);
-        mCountDownLatch = new CountDownLatch(1);
-        Executor executor = Executors.newCachedThreadPool();
-        if (!mContext.bindService(intent, Context.BIND_AUTO_CREATE, executor, mConnection)) {
-            throw new RemoteException("Failed to bind to GenerateRkpKeyService");
-        }
-        try {
-            mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            Log.e(TAG, "Interrupted: ", e);
-        }
-        if (mBinder != null) {
-            switch (command) {
-                case NOTIFY_EMPTY:
-                    returnCode = mBinder.generateKey(securityLevel);
-                    break;
-                case NOTIFY_KEY_GENERATED:
-                    mBinder.notifyKeyGenerated(securityLevel);
-                    break;
-                default:
-                    Log.e(TAG, "Invalid case for command");
-            }
-        } else {
-            Log.e(TAG, "Binder object is null; failed to bind to GenerateRkpKeyService.");
-            returnCode = IGenerateRkpKeyService.Status.INTERNAL_ERROR;
-        }
-        mContext.unbindService(mConnection);
-        return returnCode;
-    }
-
-    /**
-     * Fulfills the use case of (2) described in the class documentation. Blocks until the
-     * RemoteProvisioner application can get new attestation keys signed by the server.
-     * @return the status of the key generation
-     */
-    @CheckResult
-    @Status
-    public int notifyEmpty(int securityLevel) throws RemoteException {
-        return bindAndSendCommand(NOTIFY_EMPTY, securityLevel);
-    }
-
-    /**
-     * Fulfills the use case of (1) described in the class documentation. Non blocking call.
-     */
-    public void notifyKeyGenerated(int securityLevel) throws RemoteException {
-        bindAndSendCommand(NOTIFY_KEY_GENERATED, securityLevel);
-    }
-}
diff --git a/keystore/java/android/security/GenerateRkpKeyException.java b/keystore/java/android/security/GenerateRkpKeyException.java
deleted file mode 100644
index a2d65e4..0000000
--- a/keystore/java/android/security/GenerateRkpKeyException.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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.security;
-
-/**
- * Thrown on problems in attempting to attest to a key using a remotely provisioned key.
- *
- * @hide
- */
-public class GenerateRkpKeyException extends Exception {
-
-    /**
-     * Constructs a new {@code GenerateRkpKeyException}.
-     */
-    public GenerateRkpKeyException() {
-    }
-}
diff --git a/keystore/java/android/security/IGenerateRkpKeyService.aidl b/keystore/java/android/security/IGenerateRkpKeyService.aidl
deleted file mode 100644
index eeaeb27..0000000
--- a/keystore/java/android/security/IGenerateRkpKeyService.aidl
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * 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.security;
-
-/**
- * Interface to allow the framework to notify the RemoteProvisioner app when keys are empty. This
- * will be used if Keystore replies with an error code NO_KEYS_AVAILABLE in response to an
- * attestation request. The framework can then synchronously call generateKey() to get more
- * attestation keys generated and signed. Upon return, the caller can be certain an attestation key
- * is available.
- *
- * @hide
- */
-interface IGenerateRkpKeyService {
-    @JavaDerive(toString=true)
-    @Backing(type="int")
-    enum Status {
-        /** No error(s) occurred */
-        OK = 0,
-        /** Unable to provision keys due to a lack of internet connectivity. */
-        NO_NETWORK_CONNECTIVITY = 1,
-        /** An error occurred while communicating with the RKP server. */
-        NETWORK_COMMUNICATION_ERROR = 2,
-        /** The given device was not registered with the RKP backend. */
-        DEVICE_NOT_REGISTERED = 4,
-        /** The RKP server returned an HTTP client error, indicating a misbehaving client. */
-        HTTP_CLIENT_ERROR = 5,
-        /** The RKP server returned an HTTP server error, indicating something went wrong on the server. */
-        HTTP_SERVER_ERROR = 6,
-        /** The RKP server returned an HTTP status that is unknown. This should never happen. */
-        HTTP_UNKNOWN_ERROR = 7,
-        /** An unexpected internal error occurred. This should never happen. */
-        INTERNAL_ERROR = 8,
-    }
-
-    /**
-     * Ping the provisioner service to let it know an app generated a key. This may or may not have
-     * consumed a remotely provisioned attestation key, so the RemoteProvisioner app should check.
-     */
-    oneway void notifyKeyGenerated(in int securityLevel);
-
-    /**
-     * Ping the provisioner service to indicate there are no remaining attestation keys left.
-     */
-    Status generateKey(in int securityLevel);
-}
diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java
index c2cd6ff..74597c5 100644
--- a/keystore/java/android/security/KeyStore2.java
+++ b/keystore/java/android/security/KeyStore2.java
@@ -161,6 +161,15 @@
     }
 
     /**
+     * List all entries in the keystore for in the given namespace.
+     */
+    public KeyDescriptor[] listBatch(int domain, long namespace, String startPastAlias)
+            throws KeyStoreException {
+        return handleRemoteExceptionWithRetry(
+                (service) -> service.listEntriesBatched(domain, namespace, startPastAlias));
+    }
+
+    /**
      * Grant string prefix as used by the keystore boringssl engine. Must be kept in sync
      * with system/security/keystore-engine. Note: The prefix here includes the 0x which
      * std::stringstream used in keystore-engine needs to identify the number as hex represented.
@@ -301,6 +310,13 @@
         });
     }
 
+    /**
+     * Returns the number of Keystore entries for a given domain and namespace.
+     */
+    public int getNumberOfEntries(int domain, long namespace) throws KeyStoreException {
+        return handleRemoteExceptionWithRetry((service)
+                -> service.getNumberOfEntries(domain, namespace));
+    }
     protected static void interruptedPreservingSleep(long millis) {
         boolean wasInterrupted = false;
         Calendar calendar = Calendar.getInstance();
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index c3b0f9b..474b7ea 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,7 +20,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityThread;
 import android.content.Context;
 import android.hardware.security.keymint.EcCurve;
 import android.hardware.security.keymint.KeyParameter;
@@ -28,9 +27,6 @@
 import android.hardware.security.keymint.SecurityLevel;
 import android.hardware.security.keymint.Tag;
 import android.os.Build;
-import android.os.RemoteException;
-import android.security.GenerateRkpKey;
-import android.security.IGenerateRkpKeyService;
 import android.security.KeyPairGeneratorSpec;
 import android.security.KeyStore2;
 import android.security.KeyStoreException;
@@ -621,45 +617,6 @@
 
     @Override
     public KeyPair generateKeyPair() {
-        GenerateKeyPairHelperResult result = new GenerateKeyPairHelperResult(0, null);
-        for (int i = 0; i < 2; i++) {
-            /**
-             * NOTE: There is no need to delay between re-tries because the call to
-             * GenerateRkpKey.notifyEmpty() will delay for a while before returning.
-             */
-            result = generateKeyPairHelper();
-            if (result.rkpStatus == KeyStoreException.RKP_SUCCESS && result.keyPair != null) {
-                return result.keyPair;
-            }
-        }
-
-        // RKP failure
-        if (result.rkpStatus != KeyStoreException.RKP_SUCCESS) {
-            KeyStoreException ksException = new KeyStoreException(ResponseCode.OUT_OF_KEYS,
-                    "Could not get RKP keys", result.rkpStatus);
-            throw new ProviderException("Failed to provision new attestation keys.", ksException);
-        }
-
-        return result.keyPair;
-    }
-
-    private static class GenerateKeyPairHelperResult {
-        // Zero indicates success, non-zero indicates failure. Values should be
-        // {@link android.security.KeyStoreException#RKP_TEMPORARILY_UNAVAILABLE},
-        // {@link android.security.KeyStoreException#RKP_SERVER_REFUSED_ISSUANCE},
-        // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_CONNECTIVITY}
-        // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_SOFTWARE_REBOOT}
-        public final int rkpStatus;
-        @Nullable
-        public final KeyPair keyPair;
-
-        private GenerateKeyPairHelperResult(int rkpStatus, KeyPair keyPair) {
-            this.rkpStatus = rkpStatus;
-            this.keyPair = keyPair;
-        }
-    }
-
-    private GenerateKeyPairHelperResult generateKeyPairHelper() {
         if (mKeyStore == null || mSpec == null) {
             throw new IllegalStateException("Not initialized");
         }
@@ -697,26 +654,12 @@
             AndroidKeyStorePublicKey publicKey =
                     AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse(
                             descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm);
-            GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
-                    .currentApplication());
-            try {
-                if (mSpec.getAttestationChallenge() != null) {
-                    keyGen.notifyKeyGenerated(securityLevel);
-                }
-            } catch (RemoteException e) {
-                // This is not really an error state, and necessarily does not apply to non RKP
-                // systems or hybrid systems where RKP is not currently turned on.
-                Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend.", e);
-            }
             success = true;
-            KeyPair kp = new KeyPair(publicKey, publicKey.getPrivateKey());
-            return new GenerateKeyPairHelperResult(0, kp);
+            return new KeyPair(publicKey, publicKey.getPrivateKey());
         } catch (KeyStoreException e) {
             switch (e.getErrorCode()) {
                 case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
                     throw new StrongBoxUnavailableException("Failed to generated key pair.", e);
-                case ResponseCode.OUT_OF_KEYS:
-                    return checkIfRetryableOrThrow(e, securityLevel);
                 default:
                     ProviderException p = new ProviderException("Failed to generate key pair.", e);
                     if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
@@ -742,55 +685,6 @@
         }
     }
 
-    // In case keystore reports OUT_OF_KEYS, call this handler in an attempt to remotely provision
-    // some keys.
-    GenerateKeyPairHelperResult checkIfRetryableOrThrow(KeyStoreException e, int securityLevel) {
-        GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
-                .currentApplication());
-        KeyStoreException ksException;
-        try {
-            final int keyGenStatus = keyGen.notifyEmpty(securityLevel);
-            // Default stance: temporary error. This is a hint to the caller to try again with
-            // exponential back-off.
-            int rkpStatus;
-            switch (keyGenStatus) {
-                case IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY:
-                    rkpStatus = KeyStoreException.RKP_FETCHING_PENDING_CONNECTIVITY;
-                    break;
-                case IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED:
-                    rkpStatus = KeyStoreException.RKP_SERVER_REFUSED_ISSUANCE;
-                    break;
-                case IGenerateRkpKeyService.Status.OK:
-                    // Explicitly return not-OK here so we retry in generateKeyPair. All other cases
-                    // should throw because a retry doesn't make sense if we didn't actually
-                    // provision fresh keys.
-                    return new GenerateKeyPairHelperResult(
-                            KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE, null);
-                case IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR:
-                case IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR:
-                case IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR:
-                case IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR:
-                case IGenerateRkpKeyService.Status.INTERNAL_ERROR:
-                default:
-                    // These errors really should never happen. The best we can do is assume they
-                    // are transient and hint to the caller to retry with back-off.
-                    rkpStatus = KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE;
-                    break;
-            }
-            ksException = new KeyStoreException(
-                    ResponseCode.OUT_OF_KEYS,
-                    "Out of RKP keys due to IGenerateRkpKeyService status: " + keyGenStatus,
-                    rkpStatus);
-        } catch (RemoteException f) {
-            ksException = new KeyStoreException(
-                    ResponseCode.OUT_OF_KEYS,
-                    "Remote exception: " + f.getMessage(),
-                    KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE);
-        }
-        ksException.initCause(e);
-        throw new ProviderException("Failed to provision new attestation keys.", ksException);
-    }
-
     private void addAttestationParameters(@NonNull List<KeyParameter> params)
             throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
         byte[] challenge = mSpec.getAttestationChallenge();
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 91f216f..045e318 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -79,13 +79,11 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Set;
+import java.util.NoSuchElementException;
 
 import javax.crypto.SecretKey;
 
@@ -1043,26 +1041,22 @@
         }
     }
 
-    private Set<String> getUniqueAliases() {
+    private KeyDescriptor[] getAliasesBatch(String startPastAlias) {
         try {
-            final KeyDescriptor[] keys = mKeyStore.list(
+            return mKeyStore.listBatch(
                     getTargetDomain(),
-                    mNamespace
+                    mNamespace,
+                    startPastAlias
             );
-            final Set<String> aliases = new HashSet<>(keys.length);
-            for (KeyDescriptor d : keys) {
-                aliases.add(d.alias);
-            }
-            return aliases;
         } catch (android.security.KeyStoreException e) {
             Log.e(TAG, "Failed to list keystore entries.", e);
-            return new HashSet<>();
+            return new KeyDescriptor[0];
         }
     }
 
     @Override
     public Enumeration<String> engineAliases() {
-        return Collections.enumeration(getUniqueAliases());
+        return new KeyEntriesEnumerator();
     }
 
     @Override
@@ -1073,12 +1067,18 @@
 
         return getKeyMetadata(alias) != null;
     }
-
     @Override
     public int engineSize() {
-        return getUniqueAliases().size();
+        try {
+            return mKeyStore.getNumberOfEntries(
+                    getTargetDomain(),
+                    mNamespace
+            );
+        } catch (android.security.KeyStoreException e) {
+            Log.e(TAG, "Failed to get the number of keystore entries.", e);
+            return 0;
+        }
     }
-
     @Override
     public boolean engineIsKeyEntry(String alias) {
         return isKeyEntry(alias);
@@ -1251,4 +1251,38 @@
                             + "or TrustedCertificateEntry; was " + entry);
         }
     }
+
+    private class KeyEntriesEnumerator implements Enumeration<String> {
+        private KeyDescriptor[] mCurrentBatch;
+        private int mCurrentEntry = 0;
+        private String mLastAlias = null;
+        private KeyEntriesEnumerator() {
+            getAndValidateNextBatch();
+        }
+
+        private void getAndValidateNextBatch() {
+            mCurrentBatch = getAliasesBatch(mLastAlias);
+            mCurrentEntry = 0;
+        }
+
+        public boolean hasMoreElements() {
+            return (mCurrentBatch != null) && (mCurrentBatch.length > 0);
+        }
+
+        public String nextElement() {
+            if ((mCurrentBatch == null) || (mCurrentBatch.length == 0)) {
+                throw new NoSuchElementException("Error while fetching entries.");
+            }
+            final KeyDescriptor currentEntry = mCurrentBatch[mCurrentEntry];
+            mLastAlias = currentEntry.alias;
+
+            mCurrentEntry++;
+            // This was the last entry in the batch.
+            if (mCurrentEntry >= mCurrentBatch.length) {
+                getAndValidateNextBatch();
+            }
+
+            return mLastAlias;
+        }
+    }
 }
diff --git a/keystore/tests/src/android/security/keystore2/AndroidKeyStoreSpiTest.java b/keystore/tests/src/android/security/keystore2/AndroidKeyStoreSpiTest.java
index f96c39c8..1e1f68a 100644
--- a/keystore/tests/src/android/security/keystore2/AndroidKeyStoreSpiTest.java
+++ b/keystore/tests/src/android/security/keystore2/AndroidKeyStoreSpiTest.java
@@ -17,9 +17,14 @@
 package android.security.keystore2;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertThrows;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isNull;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.security.KeyStore2;
@@ -36,6 +41,12 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.NoSuchElementException;
+
 public class AndroidKeyStoreSpiTest {
 
     @Mock
@@ -48,14 +59,176 @@
 
     @Test
     public void testEngineAliasesReturnsEmptySetOnKeyStoreError() throws Exception {
-        when(mKeystore2.list(anyInt(), anyLong()))
+        when(mKeystore2.listBatch(anyInt(), anyLong(), isNull()))
                 .thenThrow(new KeyStoreException(6, "Some Error"));
         AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
         spi.initForTesting(mKeystore2);
 
         assertThat("Empty collection expected", !spi.engineAliases().hasMoreElements());
 
-        verify(mKeystore2).list(anyInt(), anyLong());
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), isNull());
+    }
+
+    @Test
+    public void testEngineAliasesCorrectlyListsZeroEntriesEmptyArray() throws Exception {
+        when(mKeystore2.listBatch(anyInt(), anyLong(), anyString()))
+                .thenReturn(new KeyDescriptor[0]);
+        AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
+        spi.initForTesting(mKeystore2);
+
+        Enumeration<String> aliases = spi.engineAliases();
+        assertThat("Should not have any elements", !aliases.hasMoreElements());
+        assertThrows("Should have no elements to return", NoSuchElementException.class,
+                () -> aliases.nextElement());
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), isNull());
+    }
+
+    @Test
+    public void testEngineAliasesCorrectlyListsZeroEntriesNullArray() throws Exception {
+        when(mKeystore2.listBatch(anyInt(), anyLong(), anyString()))
+                .thenReturn(null);
+        AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
+        spi.initForTesting(mKeystore2);
+
+        Enumeration<String> aliases = spi.engineAliases();
+        assertThat("Should not have any elements", !aliases.hasMoreElements());
+        assertThrows("Should have no elements to return", NoSuchElementException.class,
+                () -> aliases.nextElement());
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), isNull());
+    }
+
+    private static KeyDescriptor newKeyDescriptor(String alias) {
+        KeyDescriptor result = new KeyDescriptor();
+        result.alias = alias;
+        return result;
+    }
+
+    private static KeyDescriptor[] createKeyDescriptorsArray(int numEntries) {
+        KeyDescriptor[] kds = new KeyDescriptor[numEntries];
+        for (int i = 0; i < kds.length; i++) {
+            kds[i] = newKeyDescriptor(String.format("alias-%d", i));
+        }
+
+        return kds;
+    }
+
+    private static void assertAliasListsEqual(
+            KeyDescriptor[] keyDescriptors, Enumeration<String> aliasesEnumerator) {
+        List<String> aliases = Collections.list(aliasesEnumerator);
+        Assert.assertArrayEquals(Arrays.stream(keyDescriptors).map(kd -> kd.alias).toArray(),
+                aliases.toArray());
+    }
+
+    @Test
+    public void testEngineAliasesCorrectlyListsEntriesInASingleBatch() throws Exception {
+        final String alias1 = "testAlias1";
+        final String alias2 = "testAlias2";
+        final String alias3 = "testAlias3";
+        KeyDescriptor[] kds = {newKeyDescriptor(alias1),
+                newKeyDescriptor(alias2), newKeyDescriptor(alias3)};
+        when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
+                .thenReturn(kds);
+        when(mKeystore2.listBatch(anyInt(), anyLong(), eq("testAlias3")))
+                .thenReturn(null);
+
+        AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
+        spi.initForTesting(mKeystore2);
+
+        Enumeration<String> aliases = spi.engineAliases();
+        assertThat("Should have more elements before first.", aliases.hasMoreElements());
+        Assert.assertEquals(aliases.nextElement(), alias1);
+        assertThat("Should have more elements before second.", aliases.hasMoreElements());
+        Assert.assertEquals(aliases.nextElement(), alias2);
+        assertThat("Should have more elements before third.", aliases.hasMoreElements());
+        Assert.assertEquals(aliases.nextElement(), alias3);
+        assertThat("Should have no more elements after third.", !aliases.hasMoreElements());
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("testAlias3"));
+        verifyNoMoreInteractions(mKeystore2);
+    }
+
+    @Test
+    public void testEngineAliasesCorrectlyListsEntriesInMultipleBatches() throws Exception {
+        final int numEntries = 2500;
+        KeyDescriptor[] kds = createKeyDescriptorsArray(numEntries);
+        when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
+                .thenReturn(Arrays.copyOfRange(kds, 0, 1000));
+        when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-999")))
+                .thenReturn(Arrays.copyOfRange(kds, 1000, 2000));
+        when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-1999")))
+                .thenReturn(Arrays.copyOfRange(kds, 2000, 2500));
+        when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-2499")))
+                .thenReturn(null);
+
+        AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
+        spi.initForTesting(mKeystore2);
+
+        assertAliasListsEqual(kds, spi.engineAliases());
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-999"));
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-1999"));
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-2499"));
+        verifyNoMoreInteractions(mKeystore2);
+    }
+
+    @Test
+    public void testEngineAliasesCorrectlyListsEntriesWhenNumEntriesIsExactlyOneBatchSize()
+            throws Exception {
+        final int numEntries = 1000;
+        KeyDescriptor[] kds = createKeyDescriptorsArray(numEntries);
+        when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
+                .thenReturn(kds);
+        when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-999")))
+                .thenReturn(null);
+
+        AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
+        spi.initForTesting(mKeystore2);
+
+        assertAliasListsEqual(kds, spi.engineAliases());
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-999"));
+        verifyNoMoreInteractions(mKeystore2);
+    }
+
+    @Test
+    public void testEngineAliasesCorrectlyListsEntriesWhenNumEntriesIsAMultiplyOfBatchSize()
+            throws Exception {
+        final int numEntries = 2000;
+        KeyDescriptor[] kds = createKeyDescriptorsArray(numEntries);
+        when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
+                .thenReturn(Arrays.copyOfRange(kds, 0, 1000));
+        when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-999")))
+                .thenReturn(Arrays.copyOfRange(kds, 1000, 2000));
+        when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-1999")))
+                .thenReturn(null);
+
+        AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
+        spi.initForTesting(mKeystore2);
+
+        assertAliasListsEqual(kds, spi.engineAliases());
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-999"));
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-1999"));
+        verifyNoMoreInteractions(mKeystore2);
+    }
+
+    @Test
+    public void testEngineAliasesCorrectlyListsEntriesWhenReturningLessThanBatchSize()
+            throws Exception {
+        final int numEntries = 500;
+        KeyDescriptor[] kds = createKeyDescriptorsArray(numEntries);
+        when(mKeystore2.listBatch(anyInt(), anyLong(), eq(null)))
+                .thenReturn(kds);
+        when(mKeystore2.listBatch(anyInt(), anyLong(), eq("alias-499")))
+                .thenReturn(new KeyDescriptor[0]);
+
+        AndroidKeyStoreSpi spi = new AndroidKeyStoreSpi();
+        spi.initForTesting(mKeystore2);
+
+        assertAliasListsEqual(kds, spi.engineAliases());
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), eq(null));
+        verify(mKeystore2).listBatch(anyInt(), anyLong(), eq("alias-499"));
+        verifyNoMoreInteractions(mKeystore2);
     }
 
     @Mock
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index 852edef..f0ed6ee 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,4 +1,4 @@
 xutan@google.com
 
 # Give submodule owners in shell resource approval
-per-file res*/*/*.xml = hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com
+per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
index 926cfb3..deb7c6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -1,2 +1,3 @@
 # WM shell sub-module desktop owners
+atsjenk@google.com
 madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
index 0c2d5c4..ccbb9cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -1,2 +1,3 @@
 # WM shell sub-module freeform owners
+atsjenk@google.com
 madym@google.com
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index 1c28c3d..64dfc3e 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -7,3 +7,4 @@
 madym@google.com
 hwwang@google.com
 chenghsiuchang@google.com
+atsjenk@google.com
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 979a660..8f022ac 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -55,6 +55,10 @@
         // GCC false-positives on this warning, and since we -Werror that's
         // a problem
         "-Wno-free-nonheap-object",
+
+        // Do not de-optimise cold code paths in AFDO.
+        // Some code paths might be infrequently executed but critical to latency.
+        "-fno-profile-sample-accurate",
     ],
 
     include_dirs: [
@@ -722,6 +726,9 @@
         "tests/unit/VectorDrawableTests.cpp",
         "tests/unit/WebViewFunctorManagerTests.cpp",
     ],
+    data: [
+        ":hwuimicro",
+    ],
 }
 
 // ------------------------
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 5a67eb9..738f1ab 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -98,7 +98,7 @@
     debugOverdraw = false;
     std::string debugOverdrawProperty = base::GetProperty(PROPERTY_DEBUG_OVERDRAW, "");
     if (debugOverdrawProperty != "") {
-        INIT_LOGD("  Overdraw debug enabled: %s", debugOverdrawProperty);
+        INIT_LOGD("  Overdraw debug enabled: %s", debugOverdrawProperty.c_str());
         if (debugOverdrawProperty == "show") {
             debugOverdraw = true;
             overdrawColorSet = OverdrawColorSet::Default;
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 8e350d5..63c36f8 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -53,6 +53,8 @@
 }
 
 MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
+    bool wasSurfaceless = mEglManager.isCurrent(EGL_NO_SURFACE);
+
     // In case the surface was destroyed (e.g. a previous trimMemory call) we
     // need to recreate it here.
     if (!isSurfaceReady() && mNativeWindow) {
@@ -63,6 +65,37 @@
     if (!mEglManager.makeCurrent(mEglSurface, &error)) {
         return MakeCurrentResult::AlreadyCurrent;
     }
+
+    // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations
+    // disagree on the draw/read buffer state if the default framebuffer transitions from a surface
+    // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic.
+    // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534.
+    // The discussion was not resolved with a clear consensus
+    if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) {
+        GLint curReadFB = 0;
+        GLint curDrawFB = 0;
+        glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB);
+        glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curDrawFB);
+
+        GLint buffer = GL_NONE;
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        glGetIntegerv(GL_DRAW_BUFFER0, &buffer);
+        if (buffer == GL_NONE) {
+            const GLenum drawBuffer = GL_BACK;
+            glDrawBuffers(1, &drawBuffer);
+        }
+
+        glGetIntegerv(GL_READ_BUFFER, &buffer);
+        if (buffer == GL_NONE) {
+            glReadBuffer(GL_BACK);
+        }
+
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, curReadFB);
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, curDrawFB);
+
+        GL_CHECKPOINT(LOW);
+    }
+
     return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
 }
 
diff --git a/media/OWNERS b/media/OWNERS
index 5f50137..4a6648e 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -1,4 +1,5 @@
 # Bug component: 1344
+atneya@google.com
 elaurent@google.com
 essick@google.com
 etalvala@google.com
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index 23ee505..6e1fec2 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -12,16 +12,16 @@
       ]
     },
     {
-      "name": "GtsMediaTestCases",
+      "name": "WvtsDeviceTestCases",
       "options" : [
         {
           "include-annotation": "android.platform.test.annotations.Presubmit"
         },
         {
-          "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
+          "include-filter": "com.google.android.media.wvts.WidevineGenericOpsTests"
         },
         {
-          "include-filter": "com.google.android.media.gts.WidevineH264PlaybackTests"
+          "include-filter": "com.google.android.media.wvts.WidevineH264PlaybackTests"
         }
       ],
       "file_patterns": ["(?i)drm|crypto"]
diff --git a/media/aidl/android/media/soundtrigger_middleware/OWNERS b/media/aidl/android/media/soundtrigger_middleware/OWNERS
index 01b2cb9..1e41886 100644
--- a/media/aidl/android/media/soundtrigger_middleware/OWNERS
+++ b/media/aidl/android/media/soundtrigger_middleware/OWNERS
@@ -1,2 +1 @@
-atneya@google.com
-elaurent@google.com
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 22033c6d..6b29fc3 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -72,7 +72,7 @@
         throw new UnsupportedOperationException("Trying to instantiate AudioSystem");
     }
 
-    /* These values must be kept in sync with system/audio.h */
+    /* These values must be kept in sync with system/media/audio/include/system/audio-hal-enums.h */
     /*
      * If these are modified, please also update Settings.System.VOLUME_SETTINGS
      * and attrs.xml and AudioManager.java.
@@ -963,7 +963,8 @@
      */
 
     //
-    // audio device definitions: must be kept in sync with values in system/core/audio.h
+    // audio device definitions: must be kept in sync with values
+    // in system/media/audio/include/system/audio-hal-enums.h
     //
     /** @hide */
     public static final int DEVICE_NONE = 0x0;
diff --git a/media/java/android/media/soundtrigger/OWNERS b/media/java/android/media/soundtrigger/OWNERS
index 01b2cb9..85f7a4d 100644
--- a/media/java/android/media/soundtrigger/OWNERS
+++ b/media/java/android/media/soundtrigger/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 48436
 atneya@google.com
 elaurent@google.com
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 5b0c2a2..b878bcf5 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -43,6 +43,8 @@
 
 #include <android_runtime/android_hardware_HardwareBuffer.h>
 
+#include <android-base/stringprintf.h>
+
 #include <binder/MemoryDealer.h>
 
 #include <cutils/compiler.h>
@@ -1099,7 +1101,8 @@
                     ALOGE("Could not create MediaCodec.BufferInfo.");
                     env->ExceptionClear();
                 }
-                jniThrowException(env, "java/lang/IllegalStateException", NULL);
+                jniThrowException(env, "java/lang/IllegalStateException",
+                                  "Fatal error: could not create MediaCodec.BufferInfo object");
                 return;
             }
 
@@ -1121,7 +1124,8 @@
                     ALOGE("Could not create CodecException object.");
                     env->ExceptionClear();
                 }
-                jniThrowException(env, "java/lang/IllegalStateException", NULL);
+                jniThrowException(env, "java/lang/IllegalStateException",
+                                  "Fatal error: could not create CodecException object");
                 return;
             }
 
@@ -1134,7 +1138,9 @@
             CHECK(msg->findMessage("format", &format));
 
             if (OK != ConvertMessageToMap(env, format, &obj)) {
-                jniThrowException(env, "java/lang/IllegalStateException", NULL);
+                jniThrowException(env, "java/lang/IllegalStateException",
+                                  "Fatal error: failed to convert format "
+                                  "from native to Java object");
                 return;
             }
 
@@ -1166,7 +1172,8 @@
 
     status_t err = ConvertMessageToMap(env, data, &obj);
     if (err != OK) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        jniThrowException(env, "java/lang/IllegalStateException",
+                          "Fatal error: failed to convert format from native to Java object");
         return;
     }
 
@@ -1187,7 +1194,8 @@
 
     status_t err = ConvertMessageToMap(env, data, &obj);
     if (err != OK) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        jniThrowException(env, "java/lang/IllegalStateException",
+                          "Fatal error: failed to convert format from native to Java object");
         return;
     }
 
@@ -1198,6 +1206,18 @@
     env->DeleteLocalRef(obj);
 }
 
+std::string JMediaCodec::getExceptionMessage(const char *msg = nullptr) const {
+    if (mCodec == nullptr) {
+        return msg ?: "";
+    }
+    std::string prefix = "";
+    if (msg && msg[0] != '\0') {
+        prefix.append(msg);
+        prefix.append("\n");
+    }
+    return prefix + mCodec->getErrorLog().extract();
+}
+
 void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
     switch (msg->what()) {
         case kWhatCallbackNotify:
@@ -1347,9 +1367,17 @@
     env->Throw(exception);
 }
 
+static std::string GetExceptionMessage(const sp<JMediaCodec> &codec, const char *msg) {
+    if (codec == NULL) {
+        return msg ?: "codec is released already";
+    }
+    return codec->getExceptionMessage(msg);
+}
+
 static jint throwExceptionAsNecessary(
         JNIEnv *env, status_t err, int32_t actionCode = ACTION_CODE_FATAL,
-        const char *msg = NULL, const sp<ICrypto>& crypto = NULL) {
+        const char *msg = NULL, const sp<ICrypto>& crypto = NULL,
+        const sp<JMediaCodec> &codec = NULL) {
     switch (err) {
         case OK:
             return 0;
@@ -1364,23 +1392,38 @@
             return DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED;
 
         case INVALID_OPERATION:
-            jniThrowException(env, "java/lang/IllegalStateException", msg);
+            jniThrowException(
+                    env, "java/lang/IllegalStateException",
+                    GetExceptionMessage(codec, msg).c_str());
             return 0;
 
         case BAD_VALUE:
-            jniThrowException(env, "java/lang/IllegalArgumentException", msg);
+            jniThrowException(
+                    env, "java/lang/IllegalArgumentException",
+                    GetExceptionMessage(codec, msg).c_str());
             return 0;
 
         default:
             if (isCryptoError(err)) {
-                throwCryptoException(env, err, msg, crypto);
+                throwCryptoException(
+                        env, err,
+                        GetExceptionMessage(codec, msg).c_str(),
+                        crypto);
                 return 0;
             }
-            throwCodecException(env, err, actionCode, msg);
+            throwCodecException(
+                    env, err, actionCode,
+                    GetExceptionMessage(codec, msg).c_str());
             return 0;
     }
 }
 
+static jint throwExceptionAsNecessary(
+        JNIEnv *env, status_t err, const sp<JMediaCodec> &codec,
+        int32_t actionCode = ACTION_CODE_FATAL) {
+    return throwExceptionAsNecessary(env, err, actionCode, NULL, NULL, codec);
+}
+
 static void android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListener(
         JNIEnv *env,
         jobject thiz,
@@ -1388,13 +1431,13 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     status_t err = codec->enableOnFirstTunnelFrameReadyListener(enabled);
 
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 }
 
 static void android_media_MediaCodec_native_enableOnFrameRenderedListener(
@@ -1404,13 +1447,13 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     status_t err = codec->enableOnFrameRenderedListener(enabled);
 
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 }
 
 static void android_media_MediaCodec_native_setCallback(
@@ -1420,13 +1463,13 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     status_t err = codec->setCallback(cb);
 
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 }
 
 static void android_media_MediaCodec_native_configure(
@@ -1440,7 +1483,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
@@ -1478,7 +1521,7 @@
 
     err = codec->configure(format, bufferProducer, crypto, descrambler, flags);
 
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 }
 
 static void android_media_MediaCodec_native_setSurface(
@@ -1488,7 +1531,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
@@ -1507,7 +1550,7 @@
     }
 
     status_t err = codec->setSurface(bufferProducer);
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 }
 
 sp<PersistentSurface> android_media_MediaCodec_getPersistentInputSurface(
@@ -1611,7 +1654,7 @@
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
@@ -1625,7 +1668,7 @@
     }
     status_t err = codec->setInputSurface(persistentSurface);
     if (err != NO_ERROR) {
-        throwExceptionAsNecessary(env, err);
+        throwExceptionAsNecessary(env, err, codec);
     }
 }
 
@@ -1635,7 +1678,7 @@
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return NULL;
     }
 
@@ -1643,7 +1686,7 @@
     sp<IGraphicBufferProducer> bufferProducer;
     status_t err = codec->createInputSurface(&bufferProducer);
     if (err != NO_ERROR) {
-        throwExceptionAsNecessary(env, err);
+        throwExceptionAsNecessary(env, err, codec);
         return NULL;
     }
 
@@ -1658,13 +1701,13 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     status_t err = codec->start();
 
-    throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, "start failed");
+    throwExceptionAsNecessary(env, err, codec);
 }
 
 static void android_media_MediaCodec_stop(JNIEnv *env, jobject thiz) {
@@ -1673,13 +1716,13 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     status_t err = codec->stop();
 
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 }
 
 static void android_media_MediaCodec_reset(JNIEnv *env, jobject thiz) {
@@ -1688,7 +1731,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
@@ -1701,7 +1744,7 @@
         // trigger an IllegalStateException.
         err = UNKNOWN_ERROR;
     }
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 }
 
 static void android_media_MediaCodec_flush(JNIEnv *env, jobject thiz) {
@@ -1710,13 +1753,13 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     status_t err = codec->flush();
 
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 }
 
 static void android_media_MediaCodec_queueInputBuffer(
@@ -1732,7 +1775,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
@@ -1742,7 +1785,8 @@
             index, offset, size, timestampUs, flags, &errorDetailMsg);
 
     throwExceptionAsNecessary(
-            env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
+            env, err, ACTION_CODE_FATAL,
+            codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
 }
 
 struct NativeCryptoInfo {
@@ -1766,7 +1810,9 @@
         } else if (jmode == gCryptoModes.AesCbc) {
             mMode = CryptoPlugin::kMode_AES_CBC;
         }  else {
-            throwExceptionAsNecessary(env, INVALID_OPERATION);
+            throwExceptionAsNecessary(
+                    env, INVALID_OPERATION, ACTION_CODE_FATAL,
+                    base::StringPrintf("unrecognized crypto mode: %d", jmode).c_str());
             return;
         }
 
@@ -1902,7 +1948,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
@@ -1932,7 +1978,9 @@
     } else if (jmode == gCryptoModes.AesCbc) {
         mode = CryptoPlugin::kMode_AES_CBC;
     }  else {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(
+                env, INVALID_OPERATION, ACTION_CODE_FATAL,
+                base::StringPrintf("Unrecognized crypto mode: %d", jmode).c_str());
         return;
     }
 
@@ -2051,8 +2099,8 @@
     subSamples = NULL;
 
     throwExceptionAsNecessary(
-            env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str(),
-            codec->getCrypto());
+            env, err, ACTION_CODE_FATAL,
+            codec->getExceptionMessage(errorDetailMsg.c_str()).c_str(), codec->getCrypto());
 }
 
 static jobject android_media_MediaCodec_mapHardwareBuffer(JNIEnv *env, jclass, jobject bufferObj) {
@@ -2394,14 +2442,16 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == nullptr || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     sp<AMessage> tunings;
     status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings);
     if (err != OK) {
-        throwExceptionAsNecessary(env, err);
+        throwExceptionAsNecessary(
+                env, err, ACTION_CODE_FATAL,
+                "error occurred while converting tunings from Java to native");
         return;
     }
 
@@ -2421,15 +2471,23 @@
         }
         env->MonitorExit(lock.get());
     } else {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(
+                env, INVALID_OPERATION, ACTION_CODE_FATAL,
+                "Failed to grab lock for a LinearBlock object");
         return;
     }
 
     AString errorDetailMsg;
     if (codec->hasCryptoOrDescrambler()) {
         if (!memory) {
+            // It means there was an unexpected failure in extractMemoryFromContext above
             ALOGI("queueLinearBlock: no ashmem memory for encrypted content");
-            throwExceptionAsNecessary(env, BAD_VALUE);
+            throwExceptionAsNecessary(
+                    env, BAD_VALUE, ACTION_CODE_FATAL,
+                    "Unexpected error: the input buffer is not compatible with "
+                    "the secure codec, and a fallback logic failed.\n"
+                    "Suggestion: please try including the secure codec when calling "
+                    "MediaCodec.LinearBlock#obtain method to obtain a compatible buffer.");
             return;
         }
         auto cryptoInfo =
@@ -2453,14 +2511,22 @@
         ALOGI_IF(err != OK, "queueEncryptedLinearBlock returned err = %d", err);
     } else {
         if (!buffer) {
+            // It means there was an unexpected failure in extractBufferFromContext above
             ALOGI("queueLinearBlock: no C2Buffer found");
-            throwExceptionAsNecessary(env, BAD_VALUE);
+            throwExceptionAsNecessary(
+                    env, BAD_VALUE, ACTION_CODE_FATAL,
+                    "Unexpected error: the input buffer is not compatible with "
+                    "the non-secure codec, and a fallback logic failed.\n"
+                    "Suggestion: please do not include the secure codec when calling "
+                    "MediaCodec.LinearBlock#obtain method to obtain a compatible buffer.");
             return;
         }
         err = codec->queueBuffer(
                 index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg);
     }
-    throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str());
+    throwExceptionAsNecessary(
+            env, err, ACTION_CODE_FATAL,
+            codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
 }
 
 static void android_media_MediaCodec_native_queueHardwareBuffer(
@@ -2471,14 +2537,16 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     sp<AMessage> tunings;
     status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings);
     if (err != OK) {
-        throwExceptionAsNecessary(env, err);
+        throwExceptionAsNecessary(
+                env, err, ACTION_CODE_FATAL,
+                "error occurred while converting tunings from Java to native");
         return;
     }
 
@@ -2503,7 +2571,9 @@
         ALOGW("Failed to wrap AHardwareBuffer into C2GraphicAllocation");
         native_handle_close(handle);
         native_handle_delete(handle);
-        throwExceptionAsNecessary(env, BAD_VALUE);
+        throwExceptionAsNecessary(
+                env, BAD_VALUE, ACTION_CODE_FATAL,
+                "HardwareBuffer not recognized");
         return;
     }
     std::shared_ptr<C2GraphicBlock> block = _C2BlockFactory::CreateGraphicBlock(alloc);
@@ -2512,7 +2582,9 @@
     AString errorDetailMsg;
     err = codec->queueBuffer(
             index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg);
-    throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str());
+    throwExceptionAsNecessary(
+            env, err, ACTION_CODE_FATAL,
+            codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
 }
 
 static void android_media_MediaCodec_native_getOutputFrame(
@@ -2522,13 +2594,13 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     status_t err = codec->getOutputFrame(env, frame, index);
     if (err != OK) {
-        throwExceptionAsNecessary(env, err);
+        throwExceptionAsNecessary(env, err, codec);
     }
 }
 
@@ -2539,7 +2611,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return -1;
     }
 
@@ -2550,7 +2622,7 @@
         return (jint) index;
     }
 
-    return throwExceptionAsNecessary(env, err);
+    return throwExceptionAsNecessary(env, err, codec);
 }
 
 static jint android_media_MediaCodec_dequeueOutputBuffer(
@@ -2560,7 +2632,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return 0;
     }
 
@@ -2572,7 +2644,7 @@
         return (jint) index;
     }
 
-    return throwExceptionAsNecessary(env, err);
+    return throwExceptionAsNecessary(env, err, codec);
 }
 
 static void android_media_MediaCodec_releaseOutputBuffer(
@@ -2583,13 +2655,13 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     status_t err = codec->releaseOutputBuffer(index, render, updatePTS, timestampNs);
 
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 }
 
 static void android_media_MediaCodec_signalEndOfInputStream(JNIEnv* env,
@@ -2598,13 +2670,13 @@
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     status_t err = codec->signalEndOfInputStream();
 
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 }
 
 static jobject android_media_MediaCodec_getFormatNative(
@@ -2614,7 +2686,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return NULL;
     }
 
@@ -2625,7 +2697,7 @@
         return format;
     }
 
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 
     return NULL;
 }
@@ -2637,7 +2709,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return NULL;
     }
 
@@ -2648,7 +2720,7 @@
         return format;
     }
 
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 
     return NULL;
 }
@@ -2660,7 +2732,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return NULL;
     }
 
@@ -2673,7 +2745,7 @@
 
     // if we're out of memory, an exception was already thrown
     if (err != NO_MEMORY) {
-        throwExceptionAsNecessary(env, err);
+        throwExceptionAsNecessary(env, err, codec);
     }
 
     return NULL;
@@ -2686,7 +2758,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return NULL;
     }
 
@@ -2699,7 +2771,7 @@
 
     // if we're out of memory, an exception was already thrown
     if (err != NO_MEMORY) {
-        throwExceptionAsNecessary(env, err);
+        throwExceptionAsNecessary(env, err, codec);
     }
 
     return NULL;
@@ -2712,7 +2784,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return NULL;
     }
 
@@ -2725,7 +2797,7 @@
 
     // if we're out of memory, an exception was already thrown
     if (err != NO_MEMORY) {
-        throwExceptionAsNecessary(env, err);
+        throwExceptionAsNecessary(env, err, codec);
     }
 
     return NULL;
@@ -2738,7 +2810,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return NULL;
     }
 
@@ -2749,7 +2821,7 @@
         return name;
     }
 
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 
     return NULL;
 }
@@ -2761,7 +2833,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return NULL;
     }
 
@@ -2772,7 +2844,7 @@
         return codecInfoObj;
     }
 
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 
     return NULL;
 }
@@ -2784,7 +2856,8 @@
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
     if (codec == NULL || codec->initCheck() != OK) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        jniThrowException(env, "java/lang/IllegalStateException",
+                          GetExceptionMessage(codec, NULL).c_str());
         return 0;
     }
 
@@ -2813,7 +2886,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
@@ -2824,7 +2897,7 @@
         err = codec->setParameters(params);
     }
 
-    throwExceptionAsNecessary(env, err);
+    throwExceptionAsNecessary(env, err, codec);
 }
 
 static void android_media_MediaCodec_setVideoScalingMode(
@@ -2832,13 +2905,14 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     if (mode != NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW
             && mode != NATIVE_WINDOW_SCALING_MODE_SCALE_CROP) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                          String8::format("Unrecognized mode: %d", mode));
         return;
     }
 
@@ -2850,7 +2924,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
@@ -2862,14 +2936,14 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return NULL;
     }
 
     jobject ret = NULL;
     status_t status = codec->querySupportedVendorParameters(env, &ret);
     if (status != OK) {
-        throwExceptionAsNecessary(env, status);
+        throwExceptionAsNecessary(env, status, codec);
     }
 
     return ret;
@@ -2880,7 +2954,7 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return NULL;
     }
 
@@ -2897,13 +2971,13 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     status_t status = codec->subscribeToVendorParameters(env, names);
     if (status != OK) {
-        throwExceptionAsNecessary(env, status);
+        throwExceptionAsNecessary(env, status, codec);
     }
     return;
 }
@@ -2913,13 +2987,13 @@
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
-        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
         return;
     }
 
     status_t status = codec->unsubscribeFromVendorParameters(env, names);
     if (status != OK) {
-        throwExceptionAsNecessary(env, status);
+        throwExceptionAsNecessary(env, status, codec);
     }
     return;
 }
@@ -3310,11 +3384,15 @@
         if (!context->mReadonlyMapping) {
             const C2BufferData data = buffer->data();
             if (data.type() != C2BufferData::LINEAR) {
-                throwExceptionAsNecessary(env, INVALID_OPERATION);
+                throwExceptionAsNecessary(
+                        env, INVALID_OPERATION, ACTION_CODE_FATAL,
+                        "Underlying buffer is not a linear buffer");
                 return nullptr;
             }
             if (data.linearBlocks().size() != 1u) {
-                throwExceptionAsNecessary(env, INVALID_OPERATION);
+                throwExceptionAsNecessary(
+                        env, INVALID_OPERATION, ACTION_CODE_FATAL,
+                        "Underlying buffer contains more than one block");
                 return nullptr;
             }
             C2ConstLinearBlock block = data.linearBlocks().front();
@@ -3362,7 +3440,9 @@
                 false,  // readOnly
                 true /* clearBuffer */);
     }
-    throwExceptionAsNecessary(env, INVALID_OPERATION);
+    throwExceptionAsNecessary(
+            env, INVALID_OPERATION, ACTION_CODE_FATAL,
+            "Underlying buffer is empty");
     return nullptr;
 }
 
@@ -3385,7 +3465,9 @@
         }
         const char *cstr = env->GetStringUTFChars(jstr, nullptr);
         if (cstr == nullptr) {
-            throwExceptionAsNecessary(env, BAD_VALUE);
+            throwExceptionAsNecessary(
+                    env, BAD_VALUE, ACTION_CODE_FATAL,
+                    "Error converting Java string to native");
             return;
         }
         names->emplace_back(cstr);
@@ -3437,6 +3519,7 @@
     }
     status_t err = MediaCodec::CanFetchLinearBlock(names, &isCompatible);
     if (err != OK) {
+        // TODO: CodecErrorLog
         throwExceptionAsNecessary(env, err);
     }
     return isCompatible;
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 616c31b..fbaf64f 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -176,6 +176,8 @@
 
     const sp<ICrypto> &getCrypto() { return mCrypto; }
 
+    std::string getExceptionMessage(const char *msg) const;
+
 protected:
     virtual ~JMediaCodec();
 
diff --git a/media/jni/android_media_MediaCodecLinearBlock.h b/media/jni/android_media_MediaCodecLinearBlock.h
index c753020..060abfd 100644
--- a/media/jni/android_media_MediaCodecLinearBlock.h
+++ b/media/jni/android_media_MediaCodecLinearBlock.h
@@ -44,12 +44,19 @@
 
     std::shared_ptr<C2Buffer> toC2Buffer(size_t offset, size_t size) const {
         if (mBuffer) {
+            // TODO: if returned C2Buffer is different from mBuffer, we should
+            // find a way to connect the life cycle between this C2Buffer and
+            // mBuffer.
             if (mBuffer->data().type() != C2BufferData::LINEAR) {
                 return nullptr;
             }
             C2ConstLinearBlock block = mBuffer->data().linearBlocks().front();
             if (offset == 0 && size == block.capacity()) {
-                return mBuffer;
+                // Let C2Buffer be new one to queue to MediaCodec. It will allow
+                // the related input slot to be released by onWorkDone from C2
+                // Component. Currently, the life cycle of mBuffer should be
+                // protected by different flows.
+                return std::make_shared<C2Buffer>(*mBuffer);
             }
 
             std::shared_ptr<C2Buffer> buffer =
diff --git a/native/android/tests/activitymanager/nativeTests/Android.bp b/native/android/tests/activitymanager/nativeTests/Android.bp
index 528ac12..ebd7533 100644
--- a/native/android/tests/activitymanager/nativeTests/Android.bp
+++ b/native/android/tests/activitymanager/nativeTests/Android.bp
@@ -45,4 +45,7 @@
     required: [
         "UidImportanceHelperApp",
     ],
+    data: [
+        ":UidImportanceHelperApp",
+    ],
 }
diff --git a/packages/CtsShim/Android.bp b/packages/CtsShim/Android.bp
index 31cd760..baafe7b 100644
--- a/packages/CtsShim/Android.bp
+++ b/packages/CtsShim/Android.bp
@@ -44,6 +44,9 @@
         arm64: {
             apk: "apk/arm/CtsShimPriv.apk",
         },
+        riscv64: {
+            apk: "apk/riscv64/CtsShimPriv.apk",
+        },
         x86: {
             apk: "apk/x86/CtsShimPriv.apk",
         },
@@ -82,6 +85,9 @@
         arm64: {
             apk: "apk/arm/CtsShim.apk",
         },
+        riscv64: {
+            apk: "apk/riscv64/CtsShim.apk",
+        },
         x86: {
             apk: "apk/x86/CtsShim.apk",
         },
diff --git a/packages/CtsShim/apk/arm/CtsShim.apk b/packages/CtsShim/apk/arm/CtsShim.apk
index fb09286..af306a5 100644
--- a/packages/CtsShim/apk/arm/CtsShim.apk
+++ b/packages/CtsShim/apk/arm/CtsShim.apk
Binary files differ
diff --git a/packages/CtsShim/apk/arm/CtsShimPriv.apk b/packages/CtsShim/apk/arm/CtsShimPriv.apk
index 07915ce..98c5351 100644
--- a/packages/CtsShim/apk/arm/CtsShimPriv.apk
+++ b/packages/CtsShim/apk/arm/CtsShimPriv.apk
Binary files differ
diff --git a/packages/CtsShim/apk/riscv64/CtsShim.apk b/packages/CtsShim/apk/riscv64/CtsShim.apk
new file mode 100644
index 0000000..af306a5
--- /dev/null
+++ b/packages/CtsShim/apk/riscv64/CtsShim.apk
Binary files differ
diff --git a/packages/CtsShim/apk/riscv64/CtsShimPriv.apk b/packages/CtsShim/apk/riscv64/CtsShimPriv.apk
new file mode 100644
index 0000000..9a9997d
--- /dev/null
+++ b/packages/CtsShim/apk/riscv64/CtsShimPriv.apk
Binary files differ
diff --git a/packages/CtsShim/apk/x86/CtsShim.apk b/packages/CtsShim/apk/x86/CtsShim.apk
index fb09286..af306a5 100644
--- a/packages/CtsShim/apk/x86/CtsShim.apk
+++ b/packages/CtsShim/apk/x86/CtsShim.apk
Binary files differ
diff --git a/packages/CtsShim/apk/x86/CtsShimPriv.apk b/packages/CtsShim/apk/x86/CtsShimPriv.apk
index 20e94b6..29ad478 100644
--- a/packages/CtsShim/apk/x86/CtsShimPriv.apk
+++ b/packages/CtsShim/apk/x86/CtsShimPriv.apk
Binary files differ
diff --git a/packages/DynamicSystemInstallationService/AndroidManifest.xml b/packages/DynamicSystemInstallationService/AndroidManifest.xml
index 1765348..cd40438 100644
--- a/packages/DynamicSystemInstallationService/AndroidManifest.xml
+++ b/packages/DynamicSystemInstallationService/AndroidManifest.xml
@@ -39,6 +39,10 @@
                 <data android:scheme="http" />
                 <data android:scheme="https" />
             </intent-filter>
+            <intent-filter>
+                <action android:name="android.os.image.action.START_INSTALL" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
         </activity>
 
         <receiver
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 5562684..5e42f70 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -19,6 +19,7 @@
 import static android.os.AsyncTask.Status.FINISHED;
 import static android.os.AsyncTask.Status.PENDING;
 import static android.os.AsyncTask.Status.RUNNING;
+import static android.os.image.DynamicSystemClient.ACTION_HIDE_NOTIFICATION;
 import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_IF_IN_USE;
 import static android.os.image.DynamicSystemClient.ACTION_START_INSTALL;
 import static android.os.image.DynamicSystemClient.CAUSE_ERROR_EXCEPTION;
@@ -27,6 +28,8 @@
 import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_CANCELLED;
 import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_COMPLETED;
 import static android.os.image.DynamicSystemClient.CAUSE_NOT_SPECIFIED;
+import static android.os.image.DynamicSystemClient.KEY_ENABLE_WHEN_COMPLETED;
+import static android.os.image.DynamicSystemClient.KEY_ONE_SHOT;
 import static android.os.image.DynamicSystemClient.STATUS_IN_PROGRESS;
 import static android.os.image.DynamicSystemClient.STATUS_IN_USE;
 import static android.os.image.DynamicSystemClient.STATUS_NOT_STARTED;
@@ -77,8 +80,6 @@
 
     private static final String TAG = "DynamicSystemInstallationService";
 
-    // TODO (b/131866826): This is currently for test only. Will move this to System API.
-    static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
     static final String KEY_DSU_SLOT = "KEY_DSU_SLOT";
     static final String DEFAULT_DSU_SLOT = "dsu";
     static final String KEY_PUBKEY = "KEY_PUBKEY";
@@ -172,6 +173,8 @@
 
     // This is for testing only now
     private boolean mEnableWhenCompleted;
+    private boolean mOneShot;
+    private boolean mHideNotification;
 
     private InstallationAsyncTask.Progress mInstallTaskProgress;
     private InstallationAsyncTask mInstallTask;
@@ -229,6 +232,8 @@
             executeRebootToNormalCommand();
         } else if (ACTION_NOTIFY_IF_IN_USE.equals(action)) {
             executeNotifyIfInUseCommand();
+        } else if (ACTION_HIDE_NOTIFICATION.equals(action)) {
+            executeHideNotificationCommand();
         }
 
         return Service.START_NOT_STICKY;
@@ -318,6 +323,7 @@
         long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0);
         long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
         mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false);
+        mOneShot = intent.getBooleanExtra(KEY_ONE_SHOT, true);
         String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT);
         String publicKey = intent.getStringExtra(KEY_PUBKEY);
 
@@ -384,9 +390,9 @@
         boolean enabled = false;
 
         if (mInstallTask != null && mInstallTask.isCompleted()) {
-            enabled = mInstallTask.commit();
+            enabled = mInstallTask.commit(mOneShot);
         } else if (isDynamicSystemInstalled()) {
-            enabled = mDynSystem.setEnable(true, true);
+            enabled = mDynSystem.setEnable(true, mOneShot);
         } else {
             Log.e(TAG, "Trying to reboot to AOT while there is no complete installation");
             return;
@@ -439,12 +445,16 @@
     private void executeNotifyIfInUseCommand() {
         switch (getStatus()) {
             case STATUS_IN_USE:
-                startForeground(NOTIFICATION_ID,
-                        buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
+                if (!mHideNotification) {
+                    startForeground(NOTIFICATION_ID,
+                            buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
+                }
                 break;
             case STATUS_READY:
-                startForeground(NOTIFICATION_ID,
-                        buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
+                if (!mHideNotification) {
+                    startForeground(NOTIFICATION_ID,
+                            buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
+                }
                 break;
             case STATUS_IN_PROGRESS:
                 break;
@@ -454,6 +464,16 @@
         }
     }
 
+    private void executeHideNotificationCommand() {
+        mHideNotification = true;
+        switch (getStatus()) {
+            case STATUS_IN_USE:
+            case STATUS_READY:
+                stopForeground(STOP_FOREGROUND_REMOVE);
+                break;
+        }
+    }
+
     private void resetTaskAndStop() {
         resetTaskAndStop(/* removeNotification= */ false);
     }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index a41399f..42b620a 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -803,7 +803,7 @@
         return mIsCompleted;
     }
 
-    boolean commit() {
-        return mDynSystem.setEnable(true, true);
+    boolean commit(boolean oneShot) {
+        return mDynSystem.setEnable(true, oneShot);
     }
 }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
index 64e42cc..b522729 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.dynsystem;
 
+import static android.os.image.DynamicSystemClient.KEY_KEYGUARD_USE_DEFAULT_STRINGS;
+
 import android.app.Activity;
 import android.app.KeyguardManager;
 import android.content.Context;
@@ -47,10 +49,7 @@
         KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
 
         if (km != null) {
-            String title = getString(R.string.keyguard_title);
-            String description = getString(R.string.keyguard_description);
-            Intent intent = km.createConfirmDeviceCredentialIntent(title, description);
-
+            Intent intent = createConfirmDeviceCredentialIntent(km);
             if (intent == null) {
                 Log.d(TAG, "This device is not protected by a password/pin");
                 startInstallationService();
@@ -63,6 +62,23 @@
         }
     }
 
+    private Intent createConfirmDeviceCredentialIntent(KeyguardManager km) {
+        final boolean useDefaultStrings =
+                getIntent().getBooleanExtra(KEY_KEYGUARD_USE_DEFAULT_STRINGS, false);
+        final String title;
+        final String description;
+        if (useDefaultStrings) {
+            // Use default strings provided by keyguard manager
+            title = null;
+            description = null;
+        } else {
+            // Use custom strings provided by DSU
+            title = getString(R.string.keyguard_title);
+            description = getString(R.string.keyguard_description);
+        }
+        return km.createConfirmDeviceCredentialIntent(title, description);
+    }
+
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 6669d6b..868867d 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
           package="com.android.packageinstaller">
 
     <original-package android:name="com.android.packageinstaller" />
@@ -142,6 +143,9 @@
                   android:authorities="com.google.android.packageinstaller.wear.provider"
                   android:grantUriPermissions="true"
                   android:exported="true" />
+
+        <receiver android:name="androidx.profileinstaller.ProfileInstallReceiver"
+            tools:node="remove" />
     </application>
 
 </manifest>
diff --git a/packages/PrintSpooler/tests/outofprocess/Android.bp b/packages/PrintSpooler/tests/outofprocess/Android.bp
index 69a1d7f..ef0d122 100644
--- a/packages/PrintSpooler/tests/outofprocess/Android.bp
+++ b/packages/PrintSpooler/tests/outofprocess/Android.bp
@@ -31,7 +31,7 @@
     libs: ["android.test.runner.stubs"],
     static_libs: [
         "androidx.test.rules",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "mockito-target-minus-junit4",
         "print-test-util-lib",
     ],
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
index 132545b..1509b70 100644
--- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
@@ -35,15 +35,15 @@
 import android.print.test.services.FirstPrintService;
 import android.print.test.services.PrinterDiscoverySessionCallbacks;
 import android.print.test.services.StubbablePrinterDiscoverySession;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiSelector;
-import android.support.test.uiautomator.Until;
 import android.util.Log;
 
 import androidx.test.filters.LargeTest;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiSelector;
+import androidx.test.uiautomator.Until;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
index f305fd3..e92157e 100644
--- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
+++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
@@ -47,7 +47,7 @@
  * Annotation processor for {@link SearchIndexable} that generates {@link SearchIndexableResources}
  * subclasses.
  */
-@SupportedSourceVersion(SourceVersion.RELEASE_11)
+@SupportedSourceVersion(SourceVersion.RELEASE_17)
 @SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
 public class IndexableProcessor extends AbstractProcessor {
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index 39b4b8e..35e3dd3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -231,7 +231,7 @@
         public SignalStrength signalStrength;
         public TelephonyDisplayInfo telephonyDisplayInfo =
                 new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
 
         /**
          * Empty constructor
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/OWNERS b/packages/SettingsLib/src/com/android/settingslib/mobile/OWNERS
index ab9b5dc..c01528fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/OWNERS
@@ -1,4 +1,7 @@
 # Default reviewers for this and subdirectories.
-bonianchen@google.com
+songferngwang@google.com
+zoeychen@google.com
 
 # Emergency approvers in case the above are not available
+changbetty@google.com
+tomhsu@google.com
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/OWNERS b/packages/SettingsLib/src/com/android/settingslib/net/OWNERS
index ab9b5dc..c01528fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/net/OWNERS
@@ -1,4 +1,7 @@
 # Default reviewers for this and subdirectories.
-bonianchen@google.com
+songferngwang@google.com
+zoeychen@google.com
 
 # Emergency approvers in case the above are not available
+changbetty@google.com
+tomhsu@google.com
diff --git a/packages/SettingsProvider/src/android/provider/settings/OWNERS b/packages/SettingsProvider/src/android/provider/settings/OWNERS
index 0f88811..d901e2c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/OWNERS
+++ b/packages/SettingsProvider/src/android/provider/settings/OWNERS
@@ -1,4 +1,4 @@
 # Bug component: 656484
 
-include platform/frameworks/base:/services/backup/OWNERS
+include platform/frameworks/base:/services/backup/BACKUP_OWNERS
 
diff --git a/packages/SettingsProvider/test/src/android/provider/OWNERS b/packages/SettingsProvider/test/src/android/provider/OWNERS
index 0f88811..db4b27c 100644
--- a/packages/SettingsProvider/test/src/android/provider/OWNERS
+++ b/packages/SettingsProvider/test/src/android/provider/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 656484
 
-include platform/frameworks/base:/services/backup/OWNERS
-
+include platform/frameworks/base:/services/backup/BACKUP_OWNERS
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 0f81b0b..3889030 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -298,10 +298,16 @@
     ) {
         val view =
             openedDialogs.firstOrNull { it.dialog == animateFrom }?.dialogContentWithBackground
-                ?: throw IllegalStateException(
-                    "The animateFrom dialog was not animated using " +
-                        "DialogLaunchAnimator.showFrom(View|Dialog)"
-                )
+        if (view == null) {
+            Log.w(
+                TAG,
+                "Showing dialog $dialog normally as the dialog it is shown from was not shown " +
+                    "using DialogLaunchAnimator"
+            )
+            dialog.show()
+            return
+        }
+
         showFromView(
             dialog,
             view,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 93027c1..e7aa176 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2735,7 +2735,8 @@
 
         boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
                 && shouldListenBouncerState && shouldListenUdfpsState
-                && shouldListenSideFpsState;
+                && shouldListenSideFpsState
+                && !isFingerprintLockedOut();
         logListenerModelData(
                 new KeyguardFingerprintListenModel(
                     System.currentTimeMillis(),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 2e6ea0e..54946ee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -144,7 +144,7 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final TelephonyDisplayInfo DEFAULT_TELEPHONY_DISPLAY_INFO =
             new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
 
     static final int MAX_WIFI_ENTRY_COUNT = 3;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
index 1fb6a98..c37b01f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
@@ -49,7 +49,7 @@
 ) : ConnectivityState() {
 
     @JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
-            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE)
+            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false)
     @JvmField var serviceState: ServiceState? = null
     @JvmField var signalStrength: SignalStrength? = null
 
@@ -131,7 +131,7 @@
     }
 
     fun isRoaming(): Boolean {
-        return serviceState != null && serviceState!!.roaming
+        return telephonyDisplayInfo != null && telephonyDisplayInfo.isRoaming
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index 05e5666..29f16c7 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -272,10 +272,10 @@
 
     private static boolean showApplicationIcon(ApplicationInfo appInfo,
             PackageManager packageManager) {
-        if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP)) {
+        if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP | FLAG_SYSTEM)) {
             return packageManager.getLaunchIntentForPackage(appInfo.packageName) != null;
         }
-        return !hasFlag(appInfo.flags, FLAG_SYSTEM);
+        return true;
     }
 
     private static boolean hasFlag(int flags, int flag) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 13cd328..73e11d7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -28,6 +28,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
@@ -1040,10 +1041,11 @@
         assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked);
         assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked);
 
-        // Fingerprint should be restarted once its cancelled bc on lockout, the device
-        // can still detectFingerprint (and if it's not locked out, fingerprint can listen)
+        // Fingerprint should be cancelled on lockout if going to lockout state, else
+        // restarted if it's not
         assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
-                .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING);
+                .isEqualTo(fpLocked
+                        ? BIOMETRIC_STATE_CANCELLING : BIOMETRIC_STATE_CANCELLING_RESTARTING);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index cac4a0e..82af0f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -260,6 +260,12 @@
         assertThat(touchSurface.visibility).isEqualTo(View.GONE)
     }
 
+    @Test
+    fun showFromDialogDoesNotCrashWhenShownFromRandomDialog() {
+        val dialog = createDialogAndShowFromDialog(animateFrom = TestDialog(context))
+        dialog.dismiss()
+    }
+
     private fun createAndShowDialog(
         animator: DialogLaunchAnimator = dialogLaunchAnimator,
     ): TestDialog {
diff --git a/services/backup/BACKUP_OWNERS b/services/backup/BACKUP_OWNERS
new file mode 100644
index 0000000..f8f4f4f
--- /dev/null
+++ b/services/backup/BACKUP_OWNERS
@@ -0,0 +1,10 @@
+# Bug component: 1193469
+
+jstemmer@google.com
+martinoh@google.com
+millmore@google.com
+niamhfw@google.com
+piee@google.com
+philippov@google.com
+rthakohov@google.com
+sarpm@google.com
\ No newline at end of file
diff --git a/services/backup/OWNERS b/services/backup/OWNERS
index 79709a3..3bd2db1 100644
--- a/services/backup/OWNERS
+++ b/services/backup/OWNERS
@@ -2,12 +2,4 @@
 
 set noparent
 
-bryanmawhinney@google.com
-jstemmer@google.com
-martinoh@google.com
-millmore@google.com
-niamhfw@google.com
-piee@google.com
-philippov@google.com
-rthakohov@google.com
-sarpm@google.com
+include platform/frameworks/base:/services/backup/BACKUP_OWNERS
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index 59db686..f149fda 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -45,6 +45,7 @@
 import android.util.Slog;
 
 import com.android.internal.telephony.IMms;
+import com.android.internal.telephony.TelephonyPermissions;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriGrantsManagerInternal;
 
@@ -337,6 +338,14 @@
                 throws RemoteException {
             Slog.d(TAG, "sendMessage() by " + callingPkg);
             mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message");
+
+            // Check if user is associated with the subscription
+            if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
+                    Binder.getCallingUserHandle())) {
+                // TODO(b/258629881): Display error dialog.
+		return;
+            }
+
             if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
                     callingPkg, attributionTag, null) != AppOpsManager.MODE_ALLOWED) {
                 Slog.e(TAG, callingPkg + " is not allowed to call sendMessage()");
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 409f054..123cd328 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -27,6 +27,7 @@
 per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
 per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS
 per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
+per-file *SoundTrigger* = file:/media/java/android/media/soundtrigger/OWNERS
 per-file *Storage* = file:/core/java/android/os/storage/OWNERS
 per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
 per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index ce9c067..2daf04d 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -75,6 +75,9 @@
 import android.content.pm.UserInfo;
 import android.content.res.ObbInfo;
 import android.database.ContentObserver;
+import android.media.MediaCodecList;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
 import android.net.Uri;
 import android.os.BatteryManager;
 import android.os.Binder;
@@ -1006,10 +1009,27 @@
         }
     }
 
+    private boolean isHevcDecoderSupported() {
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
+        for (MediaCodecInfo codecInfo : codecInfos) {
+            if (codecInfo.isEncoder()) {
+                continue;
+            }
+            String[] supportedTypes = codecInfo.getSupportedTypes();
+            for (String type : supportedTypes) {
+                if (type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private void configureTranscoding() {
         // See MediaProvider TranscodeHelper#getBooleanProperty for more information
         boolean transcodeEnabled = false;
-        boolean defaultValue = true;
+        boolean defaultValue = isHevcDecoderSupported() ? true : false;
 
         if (SystemProperties.getBoolean("persist.sys.fuse.transcode_user_control", false)) {
             transcodeEnabled = SystemProperties.getBoolean("persist.sys.fuse.transcode_enabled",
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index a69d3f0..77a54a568 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1957,7 +1957,8 @@
                 && overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED) {
             overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
         }
-        return new TelephonyDisplayInfo(networkType, overrideNetworkType);
+        boolean isRoaming = telephonyDisplayInfo.isRoaming();
+        return new TelephonyDisplayInfo(networkType, overrideNetworkType, isRoaming);
     }
 
     public void notifyCallForwardingChanged(boolean cfi) {
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index f652cb0..e8c85ce 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -1074,9 +1074,10 @@
                             subGrp, mLastSnapshot, mConfigs.get(subGrp));
                     for (int restrictedTransport : restrictedTransports) {
                         if (ncCopy.hasTransport(restrictedTransport)) {
-                            if (restrictedTransport == TRANSPORT_CELLULAR) {
-                                // Only make a cell network as restricted when the VCN is in
-                                // active mode.
+                            if (restrictedTransport == TRANSPORT_CELLULAR
+                                    || restrictedTransport == TRANSPORT_TEST) {
+                                // For cell or test network, only mark it as restricted when
+                                // the VCN is in active mode.
                                 isRestricted |= (vcn.getStatus() == VCN_STATUS_CODE_ACTIVE);
                             } else {
                                 isRestricted = true;
@@ -1104,7 +1105,7 @@
             final NetworkCapabilities result = ncBuilder.build();
             final VcnUnderlyingNetworkPolicy policy = new VcnUnderlyingNetworkPolicy(
                     mTrackingNetworkCallback
-                            .requiresRestartForImmutableCapabilityChanges(result),
+                            .requiresRestartForImmutableCapabilityChanges(result, linkProperties),
                     result);
 
             logVdbg("getUnderlyingNetworkPolicy() called for caps: " + networkCapabilities
@@ -1354,19 +1355,29 @@
      * without requiring a Network restart.
      */
     private class TrackingNetworkCallback extends ConnectivityManager.NetworkCallback {
+        private final Object mLockObject = new Object();
         private final Map<Network, NetworkCapabilities> mCaps = new ArrayMap<>();
+        private final Map<Network, LinkProperties> mLinkProperties = new ArrayMap<>();
 
         @Override
         public void onCapabilitiesChanged(Network network, NetworkCapabilities caps) {
-            synchronized (mCaps) {
+            synchronized (mLockObject) {
                 mCaps.put(network, caps);
             }
         }
 
         @Override
+        public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
+            synchronized (mLockObject) {
+                mLinkProperties.put(network, lp);
+            }
+        }
+
+        @Override
         public void onLost(Network network) {
-            synchronized (mCaps) {
+            synchronized (mLockObject) {
                 mCaps.remove(network);
+                mLinkProperties.remove(network);
             }
         }
 
@@ -1393,22 +1404,28 @@
             return true;
         }
 
-        private boolean requiresRestartForImmutableCapabilityChanges(NetworkCapabilities caps) {
+        private boolean requiresRestartForImmutableCapabilityChanges(
+                NetworkCapabilities caps, LinkProperties lp) {
             if (caps.getSubscriptionIds() == null) {
                 return false;
             }
 
-            synchronized (mCaps) {
-                for (NetworkCapabilities existing : mCaps.values()) {
-                    if (caps.getSubscriptionIds().equals(existing.getSubscriptionIds())
-                            && hasSameTransportsAndCapabilities(caps, existing)) {
-                        // Restart if any immutable capabilities have changed
-                        return existing.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+            synchronized (mLockObject) {
+                // Search for an existing network (using interfce names)
+                // TODO: Get network from NetworkFactory (if exists) for this match.
+                for (Entry<Network, LinkProperties> lpEntry : mLinkProperties.entrySet()) {
+                    if (lp.getInterfaceName() != null
+                            && !lp.getInterfaceName().isEmpty()
+                            && Objects.equals(
+                                    lp.getInterfaceName(), lpEntry.getValue().getInterfaceName())) {
+                        return mCaps.get(lpEntry.getKey())
+                                        .hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
                                 != caps.hasCapability(NET_CAPABILITY_NOT_RESTRICTED);
                     }
                 }
             }
 
+            // If no network found, by definition does not need restart.
             return false;
         }
 
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 89447b4..f846741 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -3091,7 +3091,7 @@
                             }
                         }
 
-                        Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+                        Intent intent = result.getParcelable(AccountManager.KEY_INTENT, Intent.class);
                         if (intent != null && notifyOnAuthFailure && !customTokens) {
                             /*
                              * Make sure that the supplied intent is owned by the authenticator
@@ -3516,8 +3516,7 @@
             Bundle.setDefusable(result, true);
             mNumResults++;
             Intent intent = null;
-            if (result != null
-                    && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
+            if (result != null) {
                 if (!checkKeyIntent(
                         Binder.getCallingUid(),
                         result)) {
@@ -4886,8 +4885,10 @@
             	EventLog.writeEvent(0x534e4554, "250588548", authUid, "");
                 return false;
             }
-
             Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
+            if (intent == null) {
+                return true;
+            }
             // Explicitly set an empty ClipData to ensure that we don't offer to
             // promote any Uris contained inside for granting purposes
             if (intent.getClipData() == null) {
@@ -4937,8 +4938,12 @@
             Bundle simulateBundle = p.readBundle();
             p.recycle();
             Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
-            return (intent.filterEquals(simulateBundle.getParcelable(AccountManager.KEY_INTENT,
-                Intent.class)));
+            Intent simulateIntent = simulateBundle.getParcelable(AccountManager.KEY_INTENT,
+                    Intent.class);
+            if (intent == null) {
+                return (simulateIntent == null);
+            }
+            return intent.filterEquals(simulateIntent);
         }
 
         private boolean isExportedSystemActivity(ActivityInfo activityInfo) {
@@ -5087,8 +5092,7 @@
                     }
                 }
             }
-            if (result != null
-                    && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
+            if (result != null) {
                 if (!checkKeyIntent(
                         Binder.getCallingUid(),
                         result)) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 9669c06..c36e070 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3420,6 +3420,11 @@
                             throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
                                     + className + " is not an isolatedProcess");
                         }
+                        if (!mAm.getPackageManagerInternal().isSameApp(callingPackage, callingUid,
+                                userId)) {
+                            throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+                                    + "calling package not owned by calling UID ");
+                        }
                         // Run the service under the calling package's application.
                         ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo(
                                 callingPackage, ActivityManagerService.STOCK_PM_FLAGS, userId);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d48723a..0eda49e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8415,13 +8415,16 @@
             }
         }
 
+        boolean recoverable = eventType.equals("native_recoverable_crash");
+
         EventLogTags.writeAmCrash(Binder.getCallingPid(),
                 UserHandle.getUserId(Binder.getCallingUid()), processName,
                 r == null ? -1 : r.info.flags,
                 crashInfo.exceptionClassName,
                 crashInfo.exceptionMessage,
                 crashInfo.throwFileName,
-                crashInfo.throwLineNumber);
+                crashInfo.throwLineNumber,
+                recoverable ? 1 : 0);
 
         int processClassEnum = processName.equals("system_server") ? ServerProtoEnums.SYSTEM_SERVER
                 : (r != null) ? r.getProcessClassEnum()
@@ -8489,7 +8492,15 @@
                 eventType, r, processName, null, null, null, null, null, null, crashInfo,
                 new Float(loadingProgress), incrementalMetrics, null);
 
-        mAppErrors.crashApplication(r, crashInfo);
+        // For GWP-ASan recoverable crashes, don't make the app crash (the whole point of
+        // 'recoverable' is that the app doesn't crash). Normally, for nonrecoreable native crashes,
+        // debuggerd will terminate the process, but there's a backup where ActivityManager will
+        // also kill it. Avoid that.
+        if (recoverable) {
+            mAppErrors.sendRecoverableCrashToAppExitInfo(r, crashInfo);
+        } else {
+            mAppErrors.crashApplication(r, crashInfo);
+        }
     }
 
     public void handleApplicationStrictModeViolation(
@@ -14657,6 +14668,17 @@
                     throw new SecurityException(msg);
                 }
             }
+            if (!Build.IS_DEBUGGABLE && callingUid != ROOT_UID && callingUid != SHELL_UID
+                    && callingUid != SYSTEM_UID && !hasActiveInstrumentationLocked(callingPid)) {
+                // If it's not debug build and not called from root/shell/system uid, reject it.
+                final String msg = "Permission Denial: instrumentation test "
+                        + className + " from pid=" + callingPid + ", uid=" + callingUid
+                        + ", pkgName=" + getPackageNameByPid(callingPid)
+                        + " not allowed because it's not started from SHELL";
+                Slog.wtfQuiet(TAG, msg);
+                reportStartInstrumentationFailureLocked(watcher, className, msg);
+                throw new SecurityException(msg);
+            }
 
             boolean disableHiddenApiChecks = ai.usesNonSdkApi()
                     || (flags & INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS) != 0;
@@ -14879,6 +14901,29 @@
         }
     }
 
+    @GuardedBy("this")
+    private boolean hasActiveInstrumentationLocked(int pid) {
+        if (pid == 0) {
+            return false;
+        }
+        synchronized (mPidsSelfLocked) {
+            ProcessRecord process = mPidsSelfLocked.get(pid);
+            return process != null && process.getActiveInstrumentation() != null;
+        }
+    }
+
+    private String getPackageNameByPid(int pid) {
+        synchronized (mPidsSelfLocked) {
+            final ProcessRecord app = mPidsSelfLocked.get(pid);
+
+            if (app != null && app.info != null) {
+                return app.info.packageName;
+            }
+
+            return null;
+        }
+    }
+
     private boolean isCallerShell() {
         final int callingUid = Binder.getCallingUid();
         return callingUid == SHELL_UID || callingUid == ROOT_UID;
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 08c1de6..c475f00 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -553,6 +553,15 @@
         }
     }
 
+    void sendRecoverableCrashToAppExitInfo(
+            ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
+        if (r == null || crashInfo == null
+                || !"Native crash".equals(crashInfo.exceptionClassName)) return;
+        synchronized (mService) {
+            mService.mProcessList.noteAppRecoverableCrash(r);
+        }
+    }
+
     /**
      * Bring up the "unexpected error" dialog box for a crashing app.
      * Deal with edge cases (intercepts from instrumented applications,
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 32d2071..df1f3c7 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -308,6 +308,16 @@
         mKillHandler.obtainMessage(KillHandler.MSG_APP_KILL, raw).sendToTarget();
     }
 
+    void scheduleNoteAppRecoverableCrash(final ProcessRecord app) {
+        if (!mAppExitInfoLoaded.get() || app == null || app.info == null) return;
+
+        ApplicationExitInfo raw = obtainRawRecord(app, System.currentTimeMillis());
+        raw.setReason(ApplicationExitInfo.REASON_CRASH_NATIVE);
+        raw.setSubReason(ApplicationExitInfo.SUBREASON_UNKNOWN);
+        raw.setDescription("recoverable_crash");
+        mKillHandler.obtainMessage(KillHandler.MSG_APP_RECOVERABLE_CRASH, raw).sendToTarget();
+    }
+
     void scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason,
             final @SubReason int subReason, final String msg) {
         if (!mAppExitInfoLoaded.get()) {
@@ -421,8 +431,24 @@
         scheduleLogToStatsdLocked(info, true);
     }
 
+    /**
+     * Make note when ActivityManagerService gets a recoverable native crash, as the process isn't
+     * being killed but the crash should still be added to AppExitInfo. Also, because we're not
+     * crashing, don't log out to statsd.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void handleNoteAppRecoverableCrashLocked(final ApplicationExitInfo raw) {
+        addExitInfoLocked(raw, /* recoverable */ true);
+    }
+
     @GuardedBy("mLock")
     private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) {
+        return addExitInfoLocked(raw, /* recoverable */ false);
+    }
+
+    @GuardedBy("mLock")
+    private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw, boolean recoverable) {
         if (!mAppExitInfoLoaded.get()) {
             Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage");
             return null;
@@ -438,7 +464,7 @@
             }
         }
         for (int i = 0; i < packages.length; i++) {
-            addExitInfoInnerLocked(packages[i], uid, info);
+            addExitInfoInnerLocked(packages[i], uid, info, recoverable);
         }
 
         schedulePersistProcessExitInfo(false);
@@ -845,7 +871,8 @@
     }
 
     @GuardedBy("mLock")
-    private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info) {
+    private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info,
+            boolean recoverable) {
         AppExitInfoContainer container = mData.get(packageName, uid);
         if (container == null) {
             container = new AppExitInfoContainer(mAppExitInfoHistoryListSize);
@@ -859,7 +886,11 @@
             }
             mData.put(packageName, uid, container);
         }
-        container.addExitInfoLocked(info);
+        if (recoverable) {
+            container.addRecoverableCrashLocked(info);
+        } else {
+            container.addExitInfoLocked(info);
+        }
     }
 
     @GuardedBy("mLock")
@@ -1284,38 +1315,40 @@
      * A container class of {@link android.app.ApplicationExitInfo}
      */
     final class AppExitInfoContainer {
-        private SparseArray<ApplicationExitInfo> mInfos; // index is pid
+        private SparseArray<ApplicationExitInfo> mInfos; // index is a pid
+        private SparseArray<ApplicationExitInfo> mRecoverableCrashes; // index is a pid
         private int mMaxCapacity;
         private int mUid; // Application uid, not isolated uid.
 
         AppExitInfoContainer(final int maxCapacity) {
             mInfos = new SparseArray<ApplicationExitInfo>();
+            mRecoverableCrashes = new SparseArray<ApplicationExitInfo>();
             mMaxCapacity = maxCapacity;
         }
 
         @GuardedBy("mLock")
-        void getExitInfoLocked(final int filterPid, final int maxNum,
-                ArrayList<ApplicationExitInfo> results) {
+        void getInfosLocked(SparseArray<ApplicationExitInfo> map, final int filterPid,
+                final int maxNum, ArrayList<ApplicationExitInfo> results) {
             if (filterPid > 0) {
-                ApplicationExitInfo r = mInfos.get(filterPid);
+                ApplicationExitInfo r = map.get(filterPid);
                 if (r != null) {
                     results.add(r);
                 }
             } else {
-                final int numRep = mInfos.size();
+                final int numRep = map.size();
                 if (maxNum <= 0 || numRep <= maxNum) {
                     // Return all records.
                     for (int i = 0; i < numRep; i++) {
-                        results.add(mInfos.valueAt(i));
+                        results.add(map.valueAt(i));
                     }
                     Collections.sort(results,
                             (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
                 } else {
                     if (maxNum == 1) {
                         // Most of the caller might be only interested with the most recent one
-                        ApplicationExitInfo r = mInfos.valueAt(0);
+                        ApplicationExitInfo r = map.valueAt(0);
                         for (int i = 1; i < numRep; i++) {
-                            ApplicationExitInfo t = mInfos.valueAt(i);
+                            ApplicationExitInfo t = map.valueAt(i);
                             if (r.getTimestamp() < t.getTimestamp()) {
                                 r = t;
                             }
@@ -1326,7 +1359,7 @@
                         ArrayList<ApplicationExitInfo> list = mTmpInfoList2;
                         list.clear();
                         for (int i = 0; i < numRep; i++) {
-                            list.add(mInfos.valueAt(i));
+                            list.add(map.valueAt(i));
                         }
                         Collections.sort(list,
                                 (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
@@ -1340,24 +1373,30 @@
         }
 
         @GuardedBy("mLock")
-        void addExitInfoLocked(ApplicationExitInfo info) {
+        void getExitInfoLocked(final int filterPid, final int maxNum,
+                ArrayList<ApplicationExitInfo> results) {
+            getInfosLocked(mInfos, filterPid, maxNum, results);
+        }
+
+        @GuardedBy("mLock")
+        void addInfoLocked(SparseArray<ApplicationExitInfo> map, ApplicationExitInfo info) {
             int size;
-            if ((size = mInfos.size()) >= mMaxCapacity) {
+            if ((size = map.size()) >= mMaxCapacity) {
                 int oldestIndex = -1;
                 long oldestTimeStamp = Long.MAX_VALUE;
                 for (int i = 0; i < size; i++) {
-                    ApplicationExitInfo r = mInfos.valueAt(i);
+                    ApplicationExitInfo r = map.valueAt(i);
                     if (r.getTimestamp() < oldestTimeStamp) {
                         oldestTimeStamp = r.getTimestamp();
                         oldestIndex = i;
                     }
                 }
                 if (oldestIndex >= 0) {
-                    final File traceFile = mInfos.valueAt(oldestIndex).getTraceFile();
+                    final File traceFile = map.valueAt(oldestIndex).getTraceFile();
                     if (traceFile != null) {
                         traceFile.delete();
                     }
-                    mInfos.removeAt(oldestIndex);
+                    map.removeAt(oldestIndex);
                 }
             }
             // Claim the state information if there is any
@@ -1367,7 +1406,17 @@
                     mActiveAppStateSummary, uid, pid));
             info.setTraceFile(findAndRemoveFromSparse2dArray(mActiveAppTraces, uid, pid));
             info.setAppTraceRetriever(mAppTraceRetriever);
-            mInfos.append(pid, info);
+            map.append(pid, info);
+        }
+
+        @GuardedBy("mLock")
+        void addExitInfoLocked(ApplicationExitInfo info) {
+            addInfoLocked(mInfos, info);
+        }
+
+        @GuardedBy("mLock")
+        void addRecoverableCrashLocked(ApplicationExitInfo info) {
+            addInfoLocked(mRecoverableCrashes, info);
         }
 
         @GuardedBy("mLock")
@@ -1382,9 +1431,9 @@
         }
 
         @GuardedBy("mLock")
-        void destroyLocked() {
-            for (int i = mInfos.size() - 1; i >= 0; i--) {
-                ApplicationExitInfo ai = mInfos.valueAt(i);
+        void destroyLocked(SparseArray<ApplicationExitInfo> map) {
+            for (int i = map.size() - 1; i >= 0; i--) {
+                ApplicationExitInfo ai = map.valueAt(i);
                 final File traceFile = ai.getTraceFile();
                 if (traceFile != null) {
                     traceFile.delete();
@@ -1395,24 +1444,37 @@
         }
 
         @GuardedBy("mLock")
+        void destroyLocked() {
+            destroyLocked(mInfos);
+            destroyLocked(mRecoverableCrashes);
+        }
+
+        @GuardedBy("mLock")
         void forEachRecordLocked(final BiFunction<Integer, ApplicationExitInfo, Integer> callback) {
-            if (callback != null) {
-                for (int i = mInfos.size() - 1; i >= 0; i--) {
-                    switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) {
-                        case FOREACH_ACTION_REMOVE_ITEM:
-                            final File traceFile = mInfos.valueAt(i).getTraceFile();
-                            if (traceFile != null) {
-                                traceFile.delete();
-                            }
-                            mInfos.removeAt(i);
-                            break;
-                        case FOREACH_ACTION_STOP_ITERATION:
-                            i = 0;
-                            break;
-                        case FOREACH_ACTION_NONE:
-                        default:
-                            break;
-                    }
+            if (callback == null) return;
+            for (int i = mInfos.size() - 1; i >= 0; i--) {
+                switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) {
+                    case FOREACH_ACTION_STOP_ITERATION: return;
+                    case FOREACH_ACTION_REMOVE_ITEM:
+                        final File traceFile = mInfos.valueAt(i).getTraceFile();
+                        if (traceFile != null) {
+                            traceFile.delete();
+                        }
+                        mInfos.removeAt(i);
+                        break;
+                }
+            }
+            for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+                switch (callback.apply(
+                        mRecoverableCrashes.keyAt(i), mRecoverableCrashes.valueAt(i))) {
+                    case FOREACH_ACTION_STOP_ITERATION: return;
+                    case FOREACH_ACTION_REMOVE_ITEM:
+                        final File traceFile = mRecoverableCrashes.valueAt(i).getTraceFile();
+                        if (traceFile != null) {
+                            traceFile.delete();
+                        }
+                        mRecoverableCrashes.removeAt(i);
+                        break;
                 }
             }
         }
@@ -1423,6 +1485,9 @@
             for (int i = mInfos.size() - 1; i >= 0; i--) {
                 list.add(mInfos.valueAt(i));
             }
+            for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+                list.add(mRecoverableCrashes.valueAt(i));
+            }
             Collections.sort(list, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
             int size = list.size();
             for (int i = 0; i < size; i++) {
@@ -1434,10 +1499,13 @@
         void writeToProto(ProtoOutputStream proto, long fieldId) {
             long token = proto.start(fieldId);
             proto.write(AppsExitInfoProto.Package.User.UID, mUid);
-            int size = mInfos.size();
-            for (int i = 0; i < size; i++) {
+            for (int i = 0; i < mInfos.size(); i++) {
                 mInfos.valueAt(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
             }
+            for (int i = 0; i < mRecoverableCrashes.size(); i++) {
+                mRecoverableCrashes.valueAt(i).writeToProto(
+                        proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH);
+            }
             proto.end(token);
         }
 
@@ -1448,14 +1516,23 @@
                     next != ProtoInputStream.NO_MORE_FIELDS;
                     next = proto.nextField()) {
                 switch (next) {
-                    case (int) AppsExitInfoProto.Package.User.UID:
+                    case (int) AppsExitInfoProto.Package.User.UID: {
                         mUid = proto.readInt(AppsExitInfoProto.Package.User.UID);
                         break;
-                    case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO:
+                    }
+                    case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO: {
                         ApplicationExitInfo info = new ApplicationExitInfo();
                         info.readFromProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
                         mInfos.put(info.getPid(), info);
                         break;
+                    }
+                    case (int) AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH: {
+                        ApplicationExitInfo info = new ApplicationExitInfo();
+                        info.readFromProto(
+                                proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH);
+                        mRecoverableCrashes.put(info.getPid(), info);
+                        break;
+                    }
                 }
             }
             proto.end(token);
@@ -1472,6 +1549,11 @@
                     list.add(mInfos.valueAt(i));
                 }
             }
+            for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+                if (filterPid == 0 || filterPid == mRecoverableCrashes.keyAt(i)) {
+                    list.add(mRecoverableCrashes.valueAt(i));
+                }
+            }
             return list;
         }
     }
@@ -1610,6 +1692,7 @@
         static final int MSG_PROC_DIED = 4103;
         static final int MSG_APP_KILL = 4104;
         static final int MSG_STATSD_LOG = 4105;
+        static final int MSG_APP_RECOVERABLE_CRASH = 4106;
 
         KillHandler(Looper looper) {
             super(looper, null, true);
@@ -1648,6 +1731,14 @@
                     }
                 }
                 break;
+                case MSG_APP_RECOVERABLE_CRASH: {
+                    ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
+                    synchronized (mLock) {
+                        handleNoteAppRecoverableCrashLocked(raw);
+                    }
+                    recycleRawRecord(raw);
+                }
+                break;
                 default:
                     super.handleMessage(msg);
             }
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index d080036..6ce70a1 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -53,7 +53,7 @@
 30037 am_process_start_timeout (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3)
 
 # Unhandled exception
-30039 am_crash (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Exception|3),(Message|3),(File|3),(Line|1|5)
+30039 am_crash (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Exception|3),(Message|3),(File|3),(Line|1|5),(Recoverable|1|5)
 # Log.wtf() called
 30040 am_wtf (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Tag|3),(Message|3)
 
diff --git a/services/core/java/com/android/server/am/NativeCrashListener.java b/services/core/java/com/android/server/am/NativeCrashListener.java
index 94eb076..cd119e7 100644
--- a/services/core/java/com/android/server/am/NativeCrashListener.java
+++ b/services/core/java/com/android/server/am/NativeCrashListener.java
@@ -64,12 +64,15 @@
     class NativeCrashReporter extends Thread {
         ProcessRecord mApp;
         int mSignal;
+        boolean mGwpAsanRecoverableCrash;
         String mCrashReport;
 
-        NativeCrashReporter(ProcessRecord app, int signal, String report) {
+        NativeCrashReporter(ProcessRecord app, int signal, boolean gwpAsanRecoverableCrash,
+                            String report) {
             super("NativeCrashReport");
             mApp = app;
             mSignal = signal;
+            mGwpAsanRecoverableCrash = gwpAsanRecoverableCrash;
             mCrashReport = report;
         }
 
@@ -85,7 +88,9 @@
                 ci.stackTrace = mCrashReport;
 
                 if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()");
-                mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);
+                mAm.handleApplicationCrashInner(
+                        mGwpAsanRecoverableCrash ? "native_recoverable_crash" : "native_crash",
+                        mApp, mApp.processName, ci);
                 if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned");
             } catch (Exception e) {
                 Slog.e(TAG, "Unable to report native crash", e);
@@ -207,9 +212,14 @@
             // permits crash_dump to connect to it. This allows us to trust the
             // received values.
 
-            // first, the pid and signal number
-            int headerBytes = readExactly(fd, buf, 0, 8);
-            if (headerBytes != 8) {
+            // Activity Manager protocol:
+            //  - 32-bit network-byte-order: pid
+            //  - 32-bit network-byte-order: signal number
+            //  - byte: gwpAsanRecoverableCrash
+            //  - bytes: raw text of the dump
+            //  - null terminator
+            int headerBytes = readExactly(fd, buf, 0, 9);
+            if (headerBytes != 9) {
                 // protocol failure; give up
                 Slog.e(TAG, "Unable to read from debuggerd");
                 return;
@@ -217,69 +227,76 @@
 
             int pid = unpackInt(buf, 0);
             int signal = unpackInt(buf, 4);
+            boolean gwpAsanRecoverableCrash = buf[8] != 0;
             if (DEBUG) {
-                Slog.v(TAG, "Read pid=" + pid + " signal=" + signal);
+                Slog.v(TAG, "Read pid=" + pid + " signal=" + signal
+                        + " recoverable=" + gwpAsanRecoverableCrash);
+            }
+            if (pid < 0) {
+                Slog.e(TAG, "Bogus pid!");
+                return;
             }
 
             // now the text of the dump
-            if (pid > 0) {
-                final ProcessRecord pr;
-                synchronized (mAm.mPidsSelfLocked) {
-                    pr = mAm.mPidsSelfLocked.get(pid);
-                }
-                if (pr != null) {
-                    // Don't attempt crash reporting for persistent apps
-                    if (pr.isPersistent()) {
-                        if (DEBUG) {
-                            Slog.v(TAG, "Skipping report for persistent app " + pr);
-                        }
-                        return;
-                    }
-
-                    int bytes;
-                    do {
-                        // get some data
-                        bytes = Os.read(fd, buf, 0, buf.length);
-                        if (bytes > 0) {
-                            if (MORE_DEBUG) {
-                                String s = new String(buf, 0, bytes, "UTF-8");
-                                Slog.v(TAG, "READ=" + bytes + "> " + s);
-                            }
-                            // did we just get the EOD null byte?
-                            if (buf[bytes-1] == 0) {
-                                os.write(buf, 0, bytes-1);  // exclude the EOD token
-                                break;
-                            }
-                            // no EOD, so collect it and read more
-                            os.write(buf, 0, bytes);
-                        }
-                    } while (bytes > 0);
-
-                    // Okay, we've got the report.
-                    if (DEBUG) Slog.v(TAG, "processing");
-
-                    // Mark the process record as being a native crash so that the
-                    // cleanup mechanism knows we're still submitting the report
-                    // even though the process will vanish as soon as we let
-                    // debuggerd proceed.
-                    synchronized (mAm) {
-                        synchronized (mAm.mProcLock) {
-                            pr.mErrorState.setCrashing(true);
-                            pr.mErrorState.setForceCrashReport(true);
-                        }
-                    }
-
-                    // Crash reporting is synchronous but we want to let debuggerd
-                    // go about it business right away, so we spin off the actual
-                    // reporting logic on a thread and let it take it's time.
-                    final String reportString = new String(os.toByteArray(), "UTF-8");
-                    (new NativeCrashReporter(pr, signal, reportString)).start();
-                } else {
-                    Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
-                }
-            } else {
-                Slog.e(TAG, "Bogus pid!");
+            final ProcessRecord pr;
+            synchronized (mAm.mPidsSelfLocked) {
+                pr = mAm.mPidsSelfLocked.get(pid);
             }
+            if (pr == null) {
+                Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
+                return;
+            }
+
+            // Don't attempt crash reporting for persistent apps
+            if (pr.isPersistent()) {
+                if (DEBUG) {
+                    Slog.v(TAG, "Skipping report for persistent app " + pr);
+                }
+                return;
+            }
+
+            int bytes;
+            do {
+                // get some data
+                bytes = Os.read(fd, buf, 0, buf.length);
+                if (bytes > 0) {
+                    if (MORE_DEBUG) {
+                        String s = new String(buf, 0, bytes, "UTF-8");
+                        Slog.v(TAG, "READ=" + bytes + "> " + s);
+                    }
+                    // did we just get the EOD null byte?
+                    if (buf[bytes - 1] == 0) {
+                        os.write(buf, 0, bytes - 1); // exclude the EOD token
+                        break;
+                    }
+                    // no EOD, so collect it and read more
+                    os.write(buf, 0, bytes);
+                }
+            } while (bytes > 0);
+
+            // Okay, we've got the report.
+            if (DEBUG) Slog.v(TAG, "processing");
+
+            // Mark the process record as being a native crash so that the
+            // cleanup mechanism knows we're still submitting the report even
+            // though the process will vanish as soon as we let debuggerd
+            // proceed. This isn't relevant for recoverable crashes, as we don't
+            // show the user an "app crashed" dialogue because the app (by
+            // design) didn't crash.
+            if (!gwpAsanRecoverableCrash) {
+                synchronized (mAm) {
+                    synchronized (mAm.mProcLock) {
+                        pr.mErrorState.setCrashing(true);
+                        pr.mErrorState.setForceCrashReport(true);
+                    }
+                }
+            }
+
+            // Crash reporting is synchronous but we want to let debuggerd
+            // go about it business right away, so we spin off the actual
+            // reporting logic on a thread and let it take it's time.
+            final String reportString = new String(os.toByteArray(), "UTF-8");
+            (new NativeCrashReporter(pr, signal, gwpAsanRecoverableCrash, reportString)).start();
         } catch (Exception e) {
             Slog.e(TAG, "Exception dealing with report", e);
             // ugh, fail.
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index abddc43..d0607d5 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -105,6 +105,7 @@
 import android.os.UserHandle;
 import android.os.storage.StorageManagerInternal;
 import android.system.Os;
+import android.system.OsConstants;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -2328,9 +2329,15 @@
 
             if (!regularZygote) {
                 // webview and app zygote don't have the permission to create the nodes
-                if (Process.createProcessGroup(uid, startResult.pid) < 0) {
-                    throw new AssertionError("Unable to create process group for " + app.processName
-                            + " (" + startResult.pid + ")");
+                final int res = Process.createProcessGroup(uid, startResult.pid);
+                if (res < 0) {
+                    if (res == -OsConstants.ESRCH) {
+                        Slog.e(ActivityManagerService.TAG, "Unable to create process group for "
+                            + app.processName + " (" + startResult.pid + ")");
+                    } else {
+                        throw new AssertionError("Unable to create process group for "
+                            + app.processName + " (" + startResult.pid + ")");
+                    }
                 }
             }
 
@@ -2573,7 +2580,10 @@
                     + ", " + reason);
             app.setPendingStart(false);
             killProcessQuiet(pid);
-            Process.killProcessGroup(app.uid, app.getPid());
+            final int appPid = app.getPid();
+            if (appPid != 0) {
+                Process.killProcessGroup(app.uid, appPid);
+            }
             noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
                     ApplicationExitInfo.SUBREASON_INVALID_START, reason);
             return false;
@@ -5083,6 +5093,17 @@
     }
 
     /**
+     * Called by ActivityManagerService when a recoverable native crash occurs.
+     */
+    @GuardedBy("mService")
+    void noteAppRecoverableCrash(final ProcessRecord app) {
+        if (DEBUG_PROCESSES) {
+            Slog.i(TAG, "note: " + app + " has a recoverable native crash");
+        }
+        mAppExitInfoTracker.scheduleNoteAppRecoverableCrash(app);
+    }
+
+    /**
      * Called by ActivityManagerService when it decides to kill an application process.
      */
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 8886f0a..b8ff26e 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -98,7 +98,6 @@
         DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
-        DeviceConfig.NAMESPACE_TETHERING,
         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE,
         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index a74f4154..210fc910 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1209,6 +1209,9 @@
                         "LE Audio device addr=" + address + " now available").printLog(TAG));
             }
 
+            // Reset LEA suspend state each time a new sink is connected
+            mAudioSystem.setParameters("LeAudioSuspended=false");
+
             mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
                     new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
             mDeviceBroker.postAccessoryPlugMediaUnmute(device);
@@ -1254,6 +1257,9 @@
 
     @GuardedBy("mDevicesLock")
     private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) {
+        // prevent any activity on the LEA output to avoid unwanted
+        // reconnection of the sink.
+        mAudioSystem.setParameters("LeAudioSuspended=true");
         // the device will be made unavailable later, so consider it disconnected right away
         mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
         // send the delayed message to make the device unavailable later
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5ca03b8..66682cc 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -896,7 +896,7 @@
 
     // Defines the format for the connection "address" for ALSA devices
     public static String makeAlsaAddressString(int card, int device) {
-        return "card=" + card + ";device=" + device + ";";
+        return "card=" + card + ";device=" + device;
     }
 
     public static final class Lifecycle extends SystemService {
@@ -1014,9 +1014,14 @@
 
         mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase));
 
-        final boolean headTrackingDefault = mContext.getResources().getBoolean(
+        final boolean binauralEnabledDefault = SystemProperties.getBoolean(
+                "ro.audio.spatializer_binaural_enabled_default", true);
+        final boolean transauralEnabledDefault = SystemProperties.getBoolean(
+                "ro.audio.spatializer_transaural_enabled_default", true);
+        final boolean headTrackingEnabledDefault = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
-        mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, headTrackingDefault);
+        mSpatializerHelper = new SpatializerHelper(this, mAudioSystem,
+                binauralEnabledDefault, transauralEnabledDefault, headTrackingEnabledDefault);
 
         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
         mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator();
@@ -4082,13 +4087,14 @@
                 return;
         }
 
-        // Forcefully set LE audio volume as a workaround, since in some cases
-        // (like the outgoing call) the value of 'device' is not DEVICE_OUT_BLE_*
-        // even when BLE is connected.
+        // In some cases (like the outgoing or rejected call) the value of 'device' is not
+        // DEVICE_OUT_BLE_* even when BLE is connected. Changing the volume level in such case
+        // may cuase the other devices volume level leaking into the LeAudio device settings.
         if (!AudioSystem.isLeAudioDeviceType(device)) {
-            Log.w(TAG, "setLeAudioVolumeOnModeUpdate got unexpected device=" + device
-                    + ", forcing to device=" + AudioSystem.DEVICE_OUT_BLE_HEADSET);
-            device = AudioSystem.DEVICE_OUT_BLE_HEADSET;
+            Log.w(TAG, "setLeAudioVolumeOnModeUpdate ignoring invalid device="
+                    + device + ", mode=" + mode + ", index=" + index + " maxIndex=" + maxIndex
+                    + " streamType=" + streamType);
+            return;
         }
 
         if (DEBUG_VOL) {
@@ -7232,6 +7238,7 @@
         DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
         DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_LINE);
         DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
+        DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
         DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
         DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_HDMI);
     }
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 6cd42f8..d3b7606 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -477,6 +477,7 @@
         mScoAudioState = SCO_STATE_INACTIVE;
         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
         AudioSystem.setParameters("A2dpSuspended=false");
+        AudioSystem.setParameters("LeAudioSuspended=false");
         mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
     }
 
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 2b56666..c9cdba9 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -106,12 +106,12 @@
     };
 
     // Spatializer state machine
-    private static final int STATE_UNINITIALIZED = 0;
-    private static final int STATE_NOT_SUPPORTED = 1;
-    private static final int STATE_DISABLED_UNAVAILABLE = 3;
-    private static final int STATE_ENABLED_UNAVAILABLE = 4;
-    private static final int STATE_ENABLED_AVAILABLE = 5;
-    private static final int STATE_DISABLED_AVAILABLE = 6;
+    /*package*/ static final int STATE_UNINITIALIZED = 0;
+    /*package*/ static final int STATE_NOT_SUPPORTED = 1;
+    /*package*/ static final int STATE_DISABLED_UNAVAILABLE = 3;
+    /*package*/ static final int STATE_ENABLED_UNAVAILABLE = 4;
+    /*package*/ static final int STATE_ENABLED_AVAILABLE = 5;
+    /*package*/ static final int STATE_DISABLED_AVAILABLE = 6;
     private int mState = STATE_UNINITIALIZED;
 
     private boolean mFeatureEnabled = false;
@@ -147,9 +147,9 @@
             .setSampleRate(48000)
             .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
             .build();
-    // device array to store the routing for the default attributes and format, size 1 because
-    // media is never expected to be duplicated
-    private static final AudioDeviceAttributes[] ROUTING_DEVICES = new AudioDeviceAttributes[1];
+    // device array to store the routing for the default attributes and format, initialized to
+    // an empty list as routing hasn't been established yet
+    private static ArrayList<AudioDeviceAttributes> sRoutingDevices = new ArrayList<>(0);
 
     //---------------------------------------------------------------
     // audio device compatibility / enabled
@@ -171,18 +171,17 @@
     // initialization
     @SuppressWarnings("StaticAssignmentInConstructor")
     SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa,
-            boolean headTrackingEnabledByDefault) {
+            boolean binauralEnabledDefault,
+            boolean transauralEnabledDefault,
+            boolean headTrackingEnabledDefault) {
         mAudioService = mother;
         mASA = asa;
         // "StaticAssignmentInConstructor" warning is suppressed as the SpatializerHelper being
         // constructed here is the factory for SADeviceState, thus SADeviceState and its
         // private static field sHeadTrackingEnabledDefault should never be accessed directly.
-        SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledByDefault;
-    }
-
-    synchronized void initForTest(boolean hasBinaural, boolean hasTransaural) {
-        mBinauralSupported = hasBinaural;
-        mTransauralSupported = hasTransaural;
+        SADeviceState.sBinauralEnabledDefault = binauralEnabledDefault;
+        SADeviceState.sTransauralEnabledDefault = transauralEnabledDefault;
+        SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault;
     }
 
     synchronized void init(boolean effectExpected, @Nullable String settings) {
@@ -318,8 +317,7 @@
             return;
         }
         mState = STATE_DISABLED_UNAVAILABLE;
-        mASA.getDevicesForAttributes(
-                DEFAULT_ATTRIBUTES, false /* forVolume */).toArray(ROUTING_DEVICES);
+        sRoutingDevices = getRoutingDevices(DEFAULT_ATTRIBUTES);
         // note at this point mSpat is still not instantiated
     }
 
@@ -361,34 +359,35 @@
             case STATE_DISABLED_AVAILABLE:
                 break;
         }
-        mASA.getDevicesForAttributes(
-                DEFAULT_ATTRIBUTES, false /* forVolume */).toArray(ROUTING_DEVICES);
+
+        sRoutingDevices = getRoutingDevices(DEFAULT_ATTRIBUTES);
 
         // check validity of routing information
-        if (ROUTING_DEVICES[0] == null) {
-            logloge("onRoutingUpdated: device is null, no Spatial Audio");
+        if (sRoutingDevices.isEmpty()) {
+            logloge("onRoutingUpdated: no device, no Spatial Audio");
             setDispatchAvailableState(false);
             // not changing the spatializer level as this is likely a transient state
             return;
         }
+        final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
 
         // is media routed to a new device?
-        if (isWireless(ROUTING_DEVICES[0].getType())) {
-            addWirelessDeviceIfNew(ROUTING_DEVICES[0]);
+        if (isWireless(currentDevice.getType())) {
+            addWirelessDeviceIfNew(currentDevice);
         }
 
         // find if media device enabled / available
-        final Pair<Boolean, Boolean> enabledAvailable = evaluateState(ROUTING_DEVICES[0]);
+        final Pair<Boolean, Boolean> enabledAvailable = evaluateState(currentDevice);
 
         boolean able = false;
         if (enabledAvailable.second) {
             // available for Spatial audio, check w/ effect
-            able = canBeSpatializedOnDevice(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES);
+            able = canBeSpatializedOnDevice(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, sRoutingDevices);
             loglogi("onRoutingUpdated: can spatialize media 5.1:" + able
-                    + " on device:" + ROUTING_DEVICES[0]);
+                    + " on device:" + currentDevice);
             setDispatchAvailableState(able);
         } else {
-            loglogi("onRoutingUpdated: device:" + ROUTING_DEVICES[0]
+            loglogi("onRoutingUpdated: device:" + currentDevice
                     + " not available for Spatial Audio");
             setDispatchAvailableState(false);
         }
@@ -396,10 +395,10 @@
         boolean enabled = able && enabledAvailable.first;
         if (enabled) {
             loglogi("Enabling Spatial Audio since enabled for media device:"
-                    + ROUTING_DEVICES[0]);
+                    + currentDevice);
         } else {
             loglogi("Disabling Spatial Audio since disabled for media device:"
-                    + ROUTING_DEVICES[0]);
+                    + currentDevice);
         }
         if (mSpat != null) {
             byte level = enabled ? (byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL
@@ -732,9 +731,13 @@
     }
 
     private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes,
-            @NonNull AudioFormat format, @NonNull AudioDeviceAttributes[] devices) {
-        if (isDeviceCompatibleWithSpatializationModes(devices[0])) {
-            return AudioSystem.canBeSpatialized(attributes, format, devices);
+            @NonNull AudioFormat format, @NonNull ArrayList<AudioDeviceAttributes> devices) {
+        if (devices.isEmpty()) {
+            return false;
+        }
+        if (isDeviceCompatibleWithSpatializationModes(devices.get(0))) {
+            AudioDeviceAttributes[] devArray = new AudioDeviceAttributes[devices.size()];
+            return AudioSystem.canBeSpatialized(attributes, format, devices.toArray(devArray));
         }
         return false;
     }
@@ -1010,10 +1013,13 @@
                 logd("canBeSpatialized false due to usage:" + attributes.getUsage());
                 return false;
         }
-        AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1];
+
         // going through adapter to take advantage of routing cache
-        mASA.getDevicesForAttributes(
-                attributes, false /* forVolume */).toArray(devices);
+        final ArrayList<AudioDeviceAttributes> devices = getRoutingDevices(attributes);
+        if (devices.isEmpty()) {
+            logloge("canBeSpatialized got no device for " + attributes);
+            return false;
+        }
         final boolean able = canBeSpatializedOnDevice(attributes, format, devices);
         logd("canBeSpatialized usage:" + attributes.getUsage()
                 + " format:" + format.toLogFriendlyString() + " returning " + able);
@@ -1144,8 +1150,13 @@
         logDeviceState(deviceState, "setHeadTrackerEnabled");
 
         // check current routing to see if it affects the headtracking mode
-        if (ROUTING_DEVICES[0] != null && ROUTING_DEVICES[0].getType() == ada.getType()
-                && ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
+        if (sRoutingDevices.isEmpty()) {
+            logloge("setHeadTrackerEnabled: no device, bailing");
+            return;
+        }
+        final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
+        if (currentDevice.getType() == ada.getType()
+                && currentDevice.getAddress().equals(ada.getAddress())) {
             setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled
                     : Spatializer.HEAD_TRACKING_MODE_DISABLED);
             if (enabled && !mHeadTrackerAvailable) {
@@ -1539,10 +1550,12 @@
     }
 
     /*package*/ static final class SADeviceState {
+        private static boolean sBinauralEnabledDefault = true;
+        private static boolean sTransauralEnabledDefault = true;
         private static boolean sHeadTrackingEnabledDefault = false;
         final @AudioDeviceInfo.AudioDeviceType int mDeviceType;
         final @NonNull String mDeviceAddress;
-        boolean mEnabled = true;               // by default, SA is enabled on any device
+        boolean mEnabled;
         boolean mHasHeadTracker = false;
         boolean mHeadTrackerEnabled;
         static final String SETTING_FIELD_SEPARATOR = ",";
@@ -1558,6 +1571,12 @@
         SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) {
             mDeviceType = deviceType;
             mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
+            final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
+            mEnabled = spatMode == SpatializationMode.SPATIALIZER_BINAURAL
+                    ? sBinauralEnabledDefault
+                    : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
+                            ? sTransauralEnabledDefault
+                            : false;
             mHeadTrackerEnabled = sHeadTrackingEnabledDefault;
         }
 
@@ -1694,10 +1713,11 @@
 
     private int getHeadSensorHandleUpdateTracker() {
         int headHandle = -1;
-        final AudioDeviceAttributes currentDevice = ROUTING_DEVICES[0];
-        if (currentDevice == null) {
+        if (sRoutingDevices.isEmpty()) {
+            logloge("getHeadSensorHandleUpdateTracker: no device, no head tracker");
             return headHandle;
         }
+        final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
         UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice);
         // We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
         // with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
@@ -1731,6 +1751,23 @@
         return screenHandle;
     }
 
+    /**
+     * Returns routing for the given attributes
+     * @param aa AudioAttributes whose routing is being queried
+     * @return a non-null never-empty list of devices. If the routing query failed, the list
+     *     will contain null.
+     */
+    private @NonNull ArrayList<AudioDeviceAttributes> getRoutingDevices(AudioAttributes aa) {
+        final ArrayList<AudioDeviceAttributes> devices = mASA.getDevicesForAttributes(
+                aa, false /* forVolume */);
+        for (AudioDeviceAttributes ada : devices) {
+            if (ada == null) {
+                // invalid entry, reject this routing query by returning an empty list
+                return new ArrayList<>(0);
+            }
+        }
+        return devices;
+    }
 
     private static void loglogi(String msg) {
         AudioService.sSpatialLogger.loglogi(msg, TAG);
@@ -1747,4 +1784,13 @@
     /*package*/ void clearSADevices() {
         mSADevices.clear();
     }
+
+    /*package*/ synchronized void forceStateForTest(int state) {
+        mState = state;
+    }
+
+    /*package*/ synchronized void initForTest(boolean hasBinaural, boolean hasTransaural) {
+        mBinauralSupported = hasBinaural;
+        mTransauralSupported = hasTransaural;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/OWNERS b/services/core/java/com/android/server/biometrics/OWNERS
index cd281e0..1bf2aef 100644
--- a/services/core/java/com/android/server/biometrics/OWNERS
+++ b/services/core/java/com/android/server/biometrics/OWNERS
@@ -6,6 +6,10 @@
 jbolinger@google.com
 jeffpu@google.com
 joshmccloskey@google.com
+diyab@google.com
+austindelgado@google.com
+spdonghao@google.com
+wenhuiy@google.com
 
 firewall@google.com
 jasonsfchang@google.com
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 598e2b9..94b67ce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -452,6 +452,13 @@
                 return -1;
             }
 
+            if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
+                // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
+                // ever be invoked when the user is encrypted or lockdown.
+                Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
+                return -1;
+            }
+
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for detectFingerprint");
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 1e9352d1..1dc2725 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -152,6 +152,7 @@
 import com.android.internal.net.VpnProfile;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.LinkPropertiesUtils;
 import com.android.net.module.util.NetdUtils;
 import com.android.net.module.util.NetworkStackConstants;
 import com.android.server.DeviceIdleInternal;
@@ -230,7 +231,35 @@
      * <p>If retries have exceeded the length of this array, the last entry in the array will be
      * used as a repeating interval.
      */
-    private static final long[] IKEV2_VPN_RETRY_DELAYS_SEC = {1L, 2L, 5L, 30L, 60L, 300L, 900L};
+    private static final long[] IKEV2_VPN_RETRY_DELAYS_MS =
+            {1_000L, 2_000L, 5_000L, 30_000L, 60_000L, 300_000L, 900_000L};
+
+    /**
+     * A constant to pass to {@link IkeV2VpnRunner#scheduleStartIkeSession(long)} to mean the
+     * delay should be computed automatically with backoff.
+     */
+    private static final long RETRY_DELAY_AUTO_BACKOFF = -1;
+
+    /**
+     * How long to wait before trying to migrate the IKE connection when NetworkCapabilities or
+     * LinkProperties change in a way that may require migration.
+     *
+     * This delay is useful to avoid multiple migration tries (e.g. when a network changes
+     * both its NC and LP at the same time, e.g. when it first connects) and to minimize the
+     * cases where an old list of addresses is detected for the network.
+     *
+     * In practice, the IKE library reads the LinkProperties of the passed network with
+     * the synchronous {@link ConnectivityManager#getLinkProperties(Network)}, which means in
+     * most cases the race would resolve correctly, but this delay increases the chance that
+     * it correctly is.
+     * Further, using the synchronous method in the IKE library is actually dangerous because
+     * it is racy (it races with {@code IkeNetworkCallbackBase#onLost} and it should be fixed
+     * by using callbacks instead. When that happens, the race within IKE is fixed but the
+     * race between that callback and the one in IkeV2VpnRunner becomes a much bigger problem,
+     * and this delay will be necessary to ensure the correct link address list is used.
+     */
+    private static final long IKE_DELAY_ON_NC_LP_CHANGE_MS = 300;
+
     /**
      * Largest profile size allowable for Platform VPNs.
      *
@@ -271,6 +300,13 @@
     static final int DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT = 5 * 60;
 
     /**
+     * Default keepalive value to consider long-lived TCP connections are expensive on the
+     * VPN network from battery usage point of view.
+     * TODO: consider reading from setting.
+     */
+    @VisibleForTesting
+    static final int DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC = 60;
+    /**
      *  Prefer using {@link IkeSessionParams.ESP_IP_VERSION_AUTO} and
      *  {@link IkeSessionParams.ESP_ENCAP_TYPE_AUTO} for ESP packets.
      *
@@ -358,9 +394,8 @@
         return mVpnProfileStore;
     }
 
-    private static final int MAX_EVENTS_LOGS = 20;
-    private final LocalLog mUnderlyNetworkChanges = new LocalLog(MAX_EVENTS_LOGS);
-    private final LocalLog mVpnManagerEvents = new LocalLog(MAX_EVENTS_LOGS);
+    private static final int MAX_EVENTS_LOGS = 100;
+    private final LocalLog mEventChanges = new LocalLog(MAX_EVENTS_LOGS);
 
     /**
      * Cached Map of <subscription ID, CarrierConfigInfo> since retrieving the PersistableBundle
@@ -613,14 +648,14 @@
         /**
          * Retrieves the next retry delay
          *
-         * <p>If retries have exceeded the IKEV2_VPN_RETRY_DELAYS_SEC, the last entry in
+         * <p>If retries have exceeded the size of IKEV2_VPN_RETRY_DELAYS_MS, the last entry in
          * the array will be used as a repeating interval.
          */
-        public long getNextRetryDelaySeconds(int retryCount) {
-            if (retryCount >= IKEV2_VPN_RETRY_DELAYS_SEC.length) {
-                return IKEV2_VPN_RETRY_DELAYS_SEC[IKEV2_VPN_RETRY_DELAYS_SEC.length - 1];
+        public long getNextRetryDelayMs(int retryCount) {
+            if (retryCount >= IKEV2_VPN_RETRY_DELAYS_MS.length) {
+                return IKEV2_VPN_RETRY_DELAYS_MS[IKEV2_VPN_RETRY_DELAYS_MS.length - 1];
             } else {
-                return IKEV2_VPN_RETRY_DELAYS_SEC[retryCount];
+                return IKEV2_VPN_RETRY_DELAYS_MS[retryCount];
             }
         }
 
@@ -673,6 +708,14 @@
                 boolean isIpv4) {
             return MtuUtils.getMtu(childProposals, maxMtu, underlyingMtu, isIpv4);
         }
+
+        /** Verify the binder calling UID is the one passed in arguments */
+        public void verifyCallingUidAndPackage(Context context, String packageName, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            if (getAppUid(context, packageName, userId) != callingUid) {
+                throw new SecurityException(packageName + " does not belong to uid " + callingUid);
+            }
+        }
     }
 
     @VisibleForTesting
@@ -720,7 +763,7 @@
         mUserManager = mContext.getSystemService(UserManager.class);
 
         mPackage = VpnConfig.LEGACY_VPN;
-        mOwnerUID = getAppUid(mPackage, mUserId);
+        mOwnerUID = getAppUid(mContext, mPackage, mUserId);
         mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(mPackage);
 
         try {
@@ -817,7 +860,7 @@
     }
 
     /**
-     * Chooses whether to force all connections to go though VPN.
+     * Chooses whether to force all connections to go through VPN.
      *
      * Used to enable/disable legacy VPN lockdown.
      *
@@ -825,7 +868,7 @@
      * {@link #setAlwaysOnPackage(String, boolean, List<String>)}; previous settings from calling
      * that function will be replaced and saved with the always-on state.
      *
-     * @param lockdown whether to prevent all traffic outside of a VPN.
+     * @param lockdown whether to prevent all traffic outside of the VPN.
      */
     public synchronized void setLockdown(boolean lockdown) {
         enforceControlPermissionOrInternalCaller();
@@ -950,7 +993,7 @@
             int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
             @NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
             @Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
-        mVpnManagerEvents.log("Event class=" + getVpnManagerEventClassName(errorClass)
+        mEventChanges.log("[VMEvent] Event class=" + getVpnManagerEventClassName(errorClass)
                 + ", err=" + getVpnManagerEventErrorName(errorCode) + " for " + packageName
                 + " on session " + sessionKey);
         final Intent intent = buildVpnManagerEventIntent(category, errorClass, errorCode,
@@ -963,15 +1006,21 @@
         // Allow VpnManager app to temporarily run background services to handle this error.
         // If an app requires anything beyond this grace period, they MUST either declare
         // themselves as a foreground service, or schedule a job/workitem.
-        DeviceIdleInternal idleController = mDeps.getDeviceIdleInternal();
-        idleController.addPowerSaveTempWhitelistApp(Process.myUid(), packageName,
-                VPN_MANAGER_EVENT_ALLOWLIST_DURATION_MS, mUserId, false, REASON_VPN,
-                "VpnManager event");
+        final long token = Binder.clearCallingIdentity();
         try {
-            return mUserIdContext.startService(intent) != null;
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Service of VpnManager app " + intent + " failed to start", e);
-            return false;
+            final DeviceIdleInternal idleController = mDeps.getDeviceIdleInternal();
+            idleController.addPowerSaveTempWhitelistApp(Process.myUid(), packageName,
+                    VPN_MANAGER_EVENT_ALLOWLIST_DURATION_MS, mUserId, false, REASON_VPN,
+                    "VpnManager event");
+
+            try {
+                return mUserIdContext.startService(intent) != null;
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Service of VpnManager app " + intent + " failed to start", e);
+                return false;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
@@ -1096,14 +1145,24 @@
             mAlwaysOn = false;
         }
 
+        final boolean oldLockdownState = mLockdown;
         mLockdown = (mAlwaysOn && lockdown);
         mLockdownAllowlist = (mLockdown && lockdownAllowlist != null)
                 ? Collections.unmodifiableList(new ArrayList<>(lockdownAllowlist))
                 : Collections.emptyList();
+        mEventChanges.log("[LockdownAlwaysOn] Mode changed: lockdown=" + mLockdown + " alwaysOn="
+                + mAlwaysOn + " calling from " + Binder.getCallingUid());
 
         if (isCurrentPreparedPackage(packageName)) {
             updateAlwaysOnNotification(mNetworkInfo.getDetailedState());
             setVpnForcedLocked(mLockdown);
+
+            // Lockdown forces the VPN to be non-bypassable (see #agentConnect) because it makes
+            // no sense for a VPN to be bypassable when connected but not when not connected.
+            // As such, changes in lockdown need to restart the agent.
+            if (mNetworkAgent != null && oldLockdownState != mLockdown) {
+                startNewNetworkAgent(mNetworkAgent, "Lockdown mode changed");
+            }
         } else {
             // Prepare this app. The notification will update as a side-effect of updateState().
             // It also calls setVpnForcedLocked().
@@ -1341,7 +1400,8 @@
         // We can't just check that packageName matches mPackage, because if the app was uninstalled
         // and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the
         // calling package may not be the same as the prepared package. Check both UID and package.
-        return getAppUid(packageName, mUserId) == mOwnerUID && mPackage.equals(packageName);
+        return getAppUid(mContext, packageName, mUserId) == mOwnerUID
+                && mPackage.equals(packageName);
     }
 
     /** Prepare the VPN for the given package. Does not perform permission checks. */
@@ -1382,7 +1442,7 @@
 
             Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
             mPackage = newPackage;
-            mOwnerUID = getAppUid(newPackage, mUserId);
+            mOwnerUID = getAppUid(mContext, newPackage, mUserId);
             mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(newPackage);
             try {
                 mNms.allowProtect(mOwnerUID);
@@ -1403,7 +1463,7 @@
         // Check if the caller is authorized.
         enforceControlPermissionOrInternalCaller();
 
-        final int uid = getAppUid(packageName, mUserId);
+        final int uid = getAppUid(mContext, packageName, mUserId);
         if (uid == -1 || VpnConfig.LEGACY_VPN.equals(packageName)) {
             // Authorization for nonexistent packages (or fake ones) can't be updated.
             return false;
@@ -1483,11 +1543,11 @@
                 || isVpnServicePreConsented(context, packageName);
     }
 
-    private int getAppUid(final String app, final int userId) {
+    private static int getAppUid(final Context context, final String app, final int userId) {
         if (VpnConfig.LEGACY_VPN.equals(app)) {
             return Process.myUid();
         }
-        PackageManager pm = mContext.getPackageManager();
+        PackageManager pm = context.getPackageManager();
         final long token = Binder.clearCallingIdentity();
         try {
             return pm.getPackageUidAsUser(app, userId);
@@ -1616,6 +1676,10 @@
      */
     private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) {
         // NetworkAgentConfig cannot be updated without registering a new NetworkAgent.
+        // Strictly speaking, bypassability is affected by lockdown and therefore it's possible
+        // it doesn't actually change even if mConfig.allowBypass changed. It might be theoretically
+        // possible to do handover in this case, but this is far from obvious to VPN authors and
+        // it's simpler if the rule is just "can't update in place if you change allow bypass".
         if (oldConfig.allowBypass != mConfig.allowBypass) {
             Log.i(TAG, "Handover not possible due to changes to allowBypass");
             return false;
@@ -1657,10 +1721,11 @@
         mLegacyState = LegacyVpnInfo.STATE_CONNECTING;
         updateState(DetailedState.CONNECTING, "agentConnect");
 
+        final boolean bypassable = mConfig.allowBypass && !mLockdown;
         final NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig.Builder()
                 .setLegacyType(ConnectivityManager.TYPE_VPN)
                 .setLegacyTypeName("VPN")
-                .setBypassableVpn(mConfig.allowBypass && !mLockdown)
+                .setBypassableVpn(bypassable)
                 .setVpnRequiresValidation(mConfig.requiresInternetValidation)
                 .setLocalRoutesExcludedForVpn(mConfig.excludeLocalRoutes)
                 .build();
@@ -1670,9 +1735,12 @@
         capsBuilder.setUids(createUserAndRestrictedProfilesRanges(mUserId,
                 mConfig.allowedApplications, mConfig.disallowedApplications));
 
-        capsBuilder.setTransportInfo(
-                new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass,
-                        false /* longLivedTcpConnectionsExpensive */));
+        final boolean expensive = areLongLivedTcpConnectionsExpensive(mVpnRunner);
+        capsBuilder.setTransportInfo(new VpnTransportInfo(
+                getActiveVpnType(),
+                mConfig.session,
+                bypassable,
+                expensive));
 
         // Only apps targeting Q and above can explicitly declare themselves as metered.
         // These VPNs are assumed metered unless they state otherwise.
@@ -1702,6 +1770,21 @@
             Binder.restoreCallingIdentity(token);
         }
         updateState(DetailedState.CONNECTED, "agentConnect");
+        if (isIkev2VpnRunner()) {
+            final IkeSessionWrapper session = ((IkeV2VpnRunner) mVpnRunner).mSession;
+            if (null != session) session.setUnderpinnedNetwork(mNetworkAgent.getNetwork());
+        }
+    }
+
+    private static boolean areLongLivedTcpConnectionsExpensive(@NonNull VpnRunner runner) {
+        if (!(runner instanceof IkeV2VpnRunner)) return false;
+
+        final int delay = ((IkeV2VpnRunner) runner).getOrGuessKeepaliveDelaySeconds();
+        return areLongLivedTcpConnectionsExpensive(delay);
+    }
+
+    private static boolean areLongLivedTcpConnectionsExpensive(int keepaliveDelaySec) {
+        return keepaliveDelaySec < DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC;
     }
 
     private boolean canHaveRestrictedProfile(int userId) {
@@ -1715,7 +1798,7 @@
     }
 
     private void logUnderlyNetworkChanges(List<Network> networks) {
-        mUnderlyNetworkChanges.log("Switch to "
+        mEventChanges.log("[UnderlyingNW] Switch to "
                 + ((networks != null) ? TextUtils.join(", ", networks) : "null"));
     }
 
@@ -1885,7 +1968,7 @@
     private SortedSet<Integer> getAppsUids(List<String> packageNames, int userId) {
         SortedSet<Integer> uids = new TreeSet<>();
         for (String app : packageNames) {
-            int uid = getAppUid(app, userId);
+            int uid = getAppUid(mContext, app, userId);
             if (uid != -1) uids.add(uid);
             // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
             // ConnectivityServiceTest.
@@ -2982,16 +3065,17 @@
                     @Override
                     public void onCarrierConfigChanged(int slotIndex, int subId, int carrierId,
                             int specificCarrierId) {
+                        mEventChanges.log("[CarrierConfig] Changed on slot " + slotIndex + " subId="
+                                + subId + " carrerId=" + carrierId
+                                + " specificCarrierId=" + specificCarrierId);
                         synchronized (Vpn.this) {
                             mCachedCarrierConfigInfoPerSubId.remove(subId);
 
                             // Ignore stale runner.
                             if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return;
 
-                            maybeMigrateIkeSession(mActiveNetwork);
+                            maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork);
                         }
-                        // TODO: update the longLivedTcpConnectionsExpensive value in the
-                        //  networkcapabilities of the VPN network.
                     }
         };
 
@@ -3074,6 +3158,8 @@
          */
         public void onIkeOpened(int token, @NonNull IkeSessionConfiguration ikeConfiguration) {
             if (!isActiveToken(token)) {
+                mEventChanges.log("[IKEEvent-" + mSessionKey + "] onIkeOpened obsolete token="
+                        + token);
                 Log.d(TAG, "onIkeOpened called for obsolete token " + token);
                 return;
             }
@@ -3081,7 +3167,12 @@
             mMobikeEnabled =
                     ikeConfiguration.isIkeExtensionEnabled(
                             IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE);
-            onIkeConnectionInfoChanged(token, ikeConfiguration.getIkeSessionConnectionInfo());
+            final IkeSessionConnectionInfo info = ikeConfiguration.getIkeSessionConnectionInfo();
+            mEventChanges.log("[IKEEvent-" + mSessionKey + "] onIkeOpened token=" + token
+                    + ", localAddr=" + info.getLocalAddress()
+                    + ", network=" + info.getNetwork()
+                    + ", mobikeEnabled= " + mMobikeEnabled);
+            onIkeConnectionInfoChanged(token, info);
         }
 
         /**
@@ -3094,11 +3185,17 @@
          */
         public void onIkeConnectionInfoChanged(
                 int token, @NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
+
             if (!isActiveToken(token)) {
+                mEventChanges.log("[IKEEvent-" + mSessionKey
+                        + "] onIkeConnectionInfoChanged obsolete token=" + token);
                 Log.d(TAG, "onIkeConnectionInfoChanged called for obsolete token " + token);
                 return;
             }
-
+            mEventChanges.log("[IKEEvent-" + mSessionKey
+                    + "] onIkeConnectionInfoChanged token=" + token
+                    + ", localAddr=" + ikeConnectionInfo.getLocalAddress()
+                    + ", network=" + ikeConnectionInfo.getNetwork());
             // The update on VPN and the IPsec tunnel will be done when migration is fully complete
             // in onChildMigrated
             mIkeConnectionInfo = ikeConnectionInfo;
@@ -3112,6 +3209,8 @@
          */
         public void onChildOpened(int token, @NonNull ChildSessionConfiguration childConfig) {
             if (!isActiveToken(token)) {
+                mEventChanges.log("[IKEEvent-" + mSessionKey
+                        + "] onChildOpened obsolete token=" + token);
                 Log.d(TAG, "onChildOpened called for obsolete token " + token);
 
                 // Do nothing; this signals that either: (1) a new/better Network was found,
@@ -3121,7 +3220,9 @@
                 // sessions are torn down via resetIkeState().
                 return;
             }
-
+            mEventChanges.log("[IKEEvent-" + mSessionKey + "] onChildOpened token=" + token
+                    + ", addr=" + TextUtils.join(", ", childConfig.getInternalAddresses())
+                    + " dns=" + TextUtils.join(", ", childConfig.getInternalDnsServers()));
             try {
                 final String interfaceName = mTunnelIface.getInterfaceName();
                 final List<LinkAddress> internalAddresses = childConfig.getInternalAddresses();
@@ -3186,7 +3287,6 @@
                             prepareStatusIntent();
                         }
                         agentConnect(this::onValidationStatus);
-                        mSession.setUnderpinnedNetwork(mNetworkAgent.getNetwork());
                         return; // Link properties are already sent.
                     } else {
                         // Underlying networks also set in agentConnect()
@@ -3218,6 +3318,8 @@
         public void onChildTransformCreated(
                 int token, @NonNull IpSecTransform transform, int direction) {
             if (!isActiveToken(token)) {
+                mEventChanges.log("[IKEEvent-" + mSessionKey
+                        + "] onChildTransformCreated obsolete token=" + token);
                 Log.d(TAG, "ChildTransformCreated for obsolete token " + token);
 
                 // Do nothing; this signals that either: (1) a new/better Network was found,
@@ -3227,7 +3329,9 @@
                 // sessions are torn down via resetIkeState().
                 return;
             }
-
+            mEventChanges.log("[IKEEvent-" + mSessionKey
+                    + "] onChildTransformCreated token=" + token + ", direction=" + direction
+                    + ", transform=" + transform);
             try {
                 mTunnelIface.setUnderlyingNetwork(mIkeConnectionInfo.getNetwork());
 
@@ -3252,10 +3356,14 @@
                 @NonNull IpSecTransform inTransform,
                 @NonNull IpSecTransform outTransform) {
             if (!isActiveToken(token)) {
+                mEventChanges.log("[IKEEvent-" + mSessionKey
+                        + "] onChildMigrated obsolete token=" + token);
                 Log.d(TAG, "onChildMigrated for obsolete token " + token);
                 return;
             }
-
+            mEventChanges.log("[IKEEvent-" + mSessionKey
+                    + "] onChildMigrated token=" + token
+                    + ", in=" + inTransform + ", out=" + outTransform);
             // The actual network of this IKE session has migrated to is
             // mIkeConnectionInfo.getNetwork() instead of mActiveNetwork because mActiveNetwork
             // might have been updated after the migration was triggered.
@@ -3295,7 +3403,6 @@
                     if (!removedAddrs.isEmpty()) {
                         startNewNetworkAgent(
                                 mNetworkAgent, "MTU too low for IPv6; restarting network agent");
-                        mSession.setUnderpinnedNetwork(mNetworkAgent.getNetwork());
 
                         for (LinkAddress removed : removedAddrs) {
                             mTunnelIface.removeAddress(
@@ -3343,6 +3450,7 @@
          * consistency of the Ikev2VpnRunner fields.
          */
         public void onDefaultNetworkChanged(@NonNull Network network) {
+            mEventChanges.log("[UnderlyingNW] Default network changed to " + network);
             Log.d(TAG, "onDefaultNetworkChanged: " + network);
 
             // If there is a new default network brought up, cancel the retry task to prevent
@@ -3442,7 +3550,7 @@
                 return;
             }
 
-            if (maybeMigrateIkeSession(underlyingNetwork)) return;
+            if (maybeMigrateIkeSessionAndUpdateVpnTransportInfo(underlyingNetwork)) return;
 
             startIkeSession(underlyingNetwork);
         }
@@ -3549,7 +3657,44 @@
             return new CarrierConfigInfo(mccMnc, natKeepalive, encapType, ipVersion);
         }
 
-        boolean maybeMigrateIkeSession(@NonNull Network underlyingNetwork) {
+        private int getOrGuessKeepaliveDelaySeconds() {
+            if (mProfile.isAutomaticNattKeepaliveTimerEnabled()) {
+                return guessNattKeepaliveTimerForNetwork();
+            } else if (mProfile.getIkeTunnelConnectionParams() != null) {
+                return mProfile.getIkeTunnelConnectionParams()
+                        .getIkeSessionParams().getNattKeepAliveDelaySeconds();
+            }
+            return DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
+        }
+
+        boolean maybeMigrateIkeSessionAndUpdateVpnTransportInfo(
+                @NonNull Network underlyingNetwork) {
+            final int keepaliveDelaySec = getOrGuessKeepaliveDelaySeconds();
+            final boolean migrated = maybeMigrateIkeSession(underlyingNetwork, keepaliveDelaySec);
+            if (migrated) {
+                updateVpnTransportInfoAndNetCap(keepaliveDelaySec);
+            }
+            return migrated;
+        }
+
+        public void updateVpnTransportInfoAndNetCap(int keepaliveDelaySec) {
+            final VpnTransportInfo info = new VpnTransportInfo(
+                    getActiveVpnType(),
+                    mConfig.session,
+                    mConfig.allowBypass && !mLockdown,
+                    areLongLivedTcpConnectionsExpensive(keepaliveDelaySec));
+            final boolean ncUpdateRequired = !info.equals(mNetworkCapabilities.getTransportInfo());
+            if (ncUpdateRequired) {
+                mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
+                        .setTransportInfo(info)
+                        .build();
+                mEventChanges.log("[VPNRunner] Update agent caps " + mNetworkCapabilities);
+                doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
+            }
+        }
+
+        private boolean maybeMigrateIkeSession(@NonNull Network underlyingNetwork,
+                int keepaliveDelaySeconds) {
             if (mSession == null || !mMobikeEnabled) return false;
 
             // IKE session can schedule a migration event only when IKE AUTH is finished
@@ -3574,21 +3719,13 @@
                 encapType = ESP_ENCAP_TYPE_AUTO;
             }
 
-            final int keepaliveDelaySeconds;
-            if (mProfile.isAutomaticNattKeepaliveTimerEnabled()) {
-                keepaliveDelaySeconds = guessNattKeepaliveTimerForNetwork();
-            } else if (mProfile.getIkeTunnelConnectionParams() != null) {
-                keepaliveDelaySeconds = mProfile.getIkeTunnelConnectionParams()
-                        .getIkeSessionParams().getNattKeepAliveDelaySeconds();
-            } else {
-                keepaliveDelaySeconds = DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
-            }
             mSession.setNetwork(underlyingNetwork, ipVersion, encapType, keepaliveDelaySeconds);
             return true;
         }
 
         private void startIkeSession(@NonNull Network underlyingNetwork) {
             Log.d(TAG, "Start new IKE session on network " + underlyingNetwork);
+            mEventChanges.log("[IKE] Start IKE session over " + underlyingNetwork);
 
             try {
                 // Clear mInterface to prevent Ikev2VpnRunner being cleared when
@@ -3634,13 +3771,20 @@
             }
         }
 
-        private void scheduleRetryNewIkeSession() {
+        /**
+         * Schedule starting an IKE session.
+         * @param delayMs the delay after which to try starting the session. This should be
+         *                RETRY_DELAY_AUTO_BACKOFF for automatic retries with backoff.
+         */
+        private void scheduleStartIkeSession(final long delayMs) {
             if (mScheduledHandleRetryIkeSessionFuture != null) {
                 Log.d(TAG, "There is a pending retrying task, skip the new retrying task");
                 return;
             }
-            final long retryDelay = mDeps.getNextRetryDelaySeconds(mRetryCount++);
-            Log.d(TAG, "Retry new IKE session after " + retryDelay + " seconds.");
+            final long retryDelayMs = RETRY_DELAY_AUTO_BACKOFF != delayMs
+                    ? delayMs
+                    : mDeps.getNextRetryDelayMs(mRetryCount++);
+            Log.d(TAG, "Retry new IKE session after " + retryDelayMs + " milliseconds.");
             // If the default network is lost during the retry delay, the mActiveNetwork will be
             // null, and the new IKE session won't be established until there is a new default
             // network bringing up.
@@ -3651,25 +3795,35 @@
                         // Reset mScheduledHandleRetryIkeSessionFuture since it's already run on
                         // executor thread.
                         mScheduledHandleRetryIkeSessionFuture = null;
-                    }, retryDelay, TimeUnit.SECONDS);
+                    }, retryDelayMs, TimeUnit.MILLISECONDS);
         }
 
         /** Called when the NetworkCapabilities of underlying network is changed */
         public void onDefaultNetworkCapabilitiesChanged(@NonNull NetworkCapabilities nc) {
+            mEventChanges.log("[UnderlyingNW] Cap changed from "
+                    + mUnderlyingNetworkCapabilities + " to " + nc);
             final NetworkCapabilities oldNc = mUnderlyingNetworkCapabilities;
             mUnderlyingNetworkCapabilities = nc;
-            if (oldNc == null) {
-                // A new default network is available.
-                startOrMigrateIkeSession(mActiveNetwork);
-            } else if (!nc.getSubscriptionIds().equals(oldNc.getSubscriptionIds())) {
-                // Renew carrierConfig values.
-                maybeMigrateIkeSession(mActiveNetwork);
+            if (oldNc == null || !nc.getSubscriptionIds().equals(oldNc.getSubscriptionIds())) {
+                // A new default network is available, or the subscription has changed.
+                // Try to migrate the session, or failing that, start a new one.
+                scheduleStartIkeSession(IKE_DELAY_ON_NC_LP_CHANGE_MS);
             }
         }
 
         /** Called when the LinkProperties of underlying network is changed */
         public void onDefaultNetworkLinkPropertiesChanged(@NonNull LinkProperties lp) {
+            final LinkProperties oldLp = mUnderlyingLinkProperties;
+            mEventChanges.log("[UnderlyingNW] Lp changed from " + oldLp + " to " + lp);
             mUnderlyingLinkProperties = lp;
+            if (oldLp == null || !LinkPropertiesUtils.isIdenticalAllLinkAddresses(oldLp, lp)) {
+                // If some of the link addresses changed, the IKE session may need to be migrated
+                // or restarted, for example if the available IP families have changed or if the
+                // source address used has gone away. See IkeConnectionController#onNetworkSetByUser
+                // and IkeConnectionController#selectAndSetRemoteAddress for where this ends up
+                // re-evaluating the session.
+                scheduleStartIkeSession(IKE_DELAY_ON_NC_LP_CHANGE_MS);
+            }
         }
 
         class VpnConnectivityDiagnosticsCallback
@@ -3691,7 +3845,7 @@
                         Log.d(TAG, "Data stall suspected");
 
                         // Trigger MOBIKE.
-                        maybeMigrateIkeSession(mActiveNetwork);
+                        maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork);
                         mDataStallSuspected = true;
                     }
                 }
@@ -3699,6 +3853,7 @@
         }
 
         public void onValidationStatus(int status) {
+            mEventChanges.log("[Validation] validation status " + status);
             if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
                 // No data stall now. Reset it.
                 mExecutor.execute(() -> {
@@ -3739,6 +3894,7 @@
          * consistency of the Ikev2VpnRunner fields.
          */
         public void onDefaultNetworkLost(@NonNull Network network) {
+            mEventChanges.log("[UnderlyingNW] Network lost " + network);
             // If the default network is torn down, there is no need to call
             // startOrMigrateIkeSession() since it will always check if there is an active network
             // can be used or not.
@@ -3857,6 +4013,8 @@
          * consistency of the Ikev2VpnRunner fields.
          */
         public void onSessionLost(int token, @Nullable Exception exception) {
+            mEventChanges.log("[IKE] Session lost on network " + mActiveNetwork
+                    + (null == exception ? "" : " reason " + exception.getMessage()));
             Log.d(TAG, "onSessionLost() called for token " + token);
 
             if (!isActiveToken(token)) {
@@ -3943,7 +4101,7 @@
                 markFailedAndDisconnect(exception);
                 return;
             } else {
-                scheduleRetryNewIkeSession();
+                scheduleStartIkeSession(RETRY_DELAY_AUTO_BACKOFF);
             }
 
             // Close all obsolete state, but keep VPN alive incase a usable network comes up.
@@ -4013,6 +4171,7 @@
          * consistency of the Ikev2VpnRunner fields.
          */
         private void disconnectVpnRunner() {
+            mEventChanges.log("[VPNRunner] Disconnect runner, underlying network" + mActiveNetwork);
             mActiveNetwork = null;
             mUnderlyingNetworkCapabilities = null;
             mUnderlyingLinkProperties = null;
@@ -4379,10 +4538,7 @@
     }
 
     private void verifyCallingUidAndPackage(String packageName) {
-        final int callingUid = Binder.getCallingUid();
-        if (getAppUid(packageName, mUserId) != callingUid) {
-            throw new SecurityException(packageName + " does not belong to uid " + callingUid);
-        }
+        mDeps.verifyCallingUidAndPackage(mContext, packageName, mUserId);
     }
 
     @VisibleForTesting
@@ -4673,7 +4829,7 @@
         // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
         //  ConnectivityServiceTest.
         if (SdkLevel.isAtLeastT()) {
-            mVpnManagerEvents.log(packageName + " stopped");
+            mEventChanges.log("[VMEvent] " + packageName + " stopped");
             sendEventToVpnManagerApp(intent, packageName);
         }
     }
@@ -5007,23 +5163,21 @@
             pw.println("NetworkCapabilities: " + mNetworkCapabilities);
             if (isIkev2VpnRunner()) {
                 final IkeV2VpnRunner runner = ((IkeV2VpnRunner) mVpnRunner);
-                pw.println("Token: " + runner.mSessionKey);
+                pw.println("SessionKey: " + runner.mSessionKey);
                 pw.println("MOBIKE " + (runner.mMobikeEnabled ? "enabled" : "disabled"));
+                pw.println("Profile: " + runner.mProfile);
+                pw.println("Token: " + runner.mCurrentToken);
                 if (mDataStallSuspected) pw.println("Data stall suspected");
                 if (runner.mScheduledHandleDataStallFuture != null) {
                     pw.println("Reset session scheduled");
                 }
             }
+            pw.println();
             pw.println("mCachedCarrierConfigInfoPerSubId=" + mCachedCarrierConfigInfoPerSubId);
 
-            pw.println("mUnderlyNetworkChanges (most recent first):");
+            pw.println("mEventChanges (most recent first):");
             pw.increaseIndent();
-            mUnderlyNetworkChanges.reverseDump(pw);
-            pw.decreaseIndent();
-
-            pw.println("mVpnManagerEvent (most recent first):");
-            pw.increaseIndent();
-            mVpnManagerEvents.reverseDump(pw);
+            mEventChanges.reverseDump(pw);
             pw.decreaseIndent();
         }
     }
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 8b579ac..6cc89b8 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -883,7 +883,7 @@
             if (mLoggingEnabled) {
                 Slog.d(TAG, "updateAmbientLux: "
                         + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
-                        + "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", "
+                        + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", "
                         + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", "
                         + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
                         + "mAmbientLux=" + mAmbientLux);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 54189bf7..237485b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1670,9 +1670,11 @@
             // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
             // done in HighBrightnessModeController.
             if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
-                    && (mBrightnessReason.modifier & BrightnessReason.MODIFIER_DIMMED) == 0
-                    && (mBrightnessReason.modifier & BrightnessReason.MODIFIER_LOW_POWER) == 0) {
-                // We want to scale HDR brightness level with the SDR level
+                    && (mBrightnessReasonTemp.modifier & BrightnessReason.MODIFIER_DIMMED) == 0
+                    && (mBrightnessReasonTemp.modifier & BrightnessReason.MODIFIER_LOW_POWER)
+                    == 0) {
+                // We want to scale HDR brightness level with the SDR level, we also need to restore
+                // SDR brightness immediately when entering dim or low power mode.
                 animateValue = mHbmController.getHdrBrightnessValue();
             }
 
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index 73131a1..06dd500 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -300,8 +300,11 @@
     }
 
     public boolean setBrightness(DisplayDevice displayDevice, float brightness) {
+        if (displayDevice == null || !displayDevice.hasStableUniqueId()) {
+            return false;
+        }
         final String displayDeviceUniqueId = displayDevice.getUniqueId();
-        if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
+        if (displayDeviceUniqueId == null) {
             return false;
         }
         final DisplayState state = getDisplayState(displayDeviceUniqueId, true);
diff --git a/services/core/java/com/android/server/infra/OWNERS b/services/core/java/com/android/server/infra/OWNERS
index 0466d8a..4fea05d 100644
--- a/services/core/java/com/android/server/infra/OWNERS
+++ b/services/core/java/com/android/server/infra/OWNERS
@@ -1,3 +1,3 @@
 # Bug component: 655446
 
-include /core/java/android/service/cloudsearch/OWNERS
+srazdan@google.com
diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS
index 00cd700..6e5eb56 100644
--- a/services/core/java/com/android/server/inputmethod/OWNERS
+++ b/services/core/java/com/android/server/inputmethod/OWNERS
@@ -1,8 +1,8 @@
 set noparent
 
-ogunwale@google.com
+roosa@google.com
 yukawa@google.com
 tarandeep@google.com
-lumark@google.com
-roosa@google.com
-wilsonwu@google.com
+
+ogunwale@google.com #{LAST_RESORT_SUGGESTION}
+jjaggi@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 1235352..f0aff2a 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -300,6 +300,7 @@
         public void deliverOnFlushComplete(int requestCode) throws PendingIntent.CanceledException {
             BroadcastOptions options = BroadcastOptions.makeBasic();
             options.setDontSendToRestrictedApps(true);
+            options.setPendingIntentBackgroundActivityLaunchAllowed(false);
 
             mPendingIntent.send(mContext, 0, new Intent().putExtra(KEY_FLUSH_COMPLETE, requestCode),
                     null, null, null, options.toBundle());
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 65bd3f1..0f13c2c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -108,7 +108,7 @@
     @VisibleForTesting
     static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000;
     @VisibleForTesting
-    static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 50000;
+    static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 6000;
 
     private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000;
     private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000;
diff --git a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
index b4bcd5b..5de1559 100644
--- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
+++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
@@ -61,7 +61,7 @@
     private static final String AVC_PREFIX = "type=" + AUDIT_AVC + " ";
 
     private static final Pattern EXECUTE_NATIVE_AUDIT_PATTERN =
-            Pattern.compile(".*\\bavc: granted \\{ execute(?:_no_trans|) \\} .*"
+            Pattern.compile(".*\\bavc: +granted +\\{ execute(?:_no_trans|) \\} .*"
                     + "\\bpath=(?:\"([^\" ]*)\"|([0-9A-F]+)) .*"
                     + "\\bscontext=u:r:untrusted_app(?:_25|_27)?:.*"
                     + "\\btcontext=u:object_r:app_data_file:.*"
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 3816b07..6a40a3e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -94,6 +94,7 @@
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.ApplicationPackageManager;
+import android.app.BroadcastOptions;
 import android.app.backup.IBackupManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -641,7 +642,10 @@
         fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
                 PackageManager.installStatusToPublicStatus(returnCode));
         try {
-            target.sendIntent(context, 0, fillIn, null, null);
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+            options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+            target.sendIntent(context, 0, fillIn, null /* onFinished*/, null /* handler */,
+                    null /* requiredPermission */, options.toBundle());
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
@@ -3938,10 +3942,14 @@
                     deletePackageHelper.deletePackageLIF(parsedPackage.getPackageName(), null, true,
                             mPm.mUserManager.getUserIds(), 0, null, false);
                 }
-            } else if (newPkgVersionGreater) {
+            } else if (newPkgVersionGreater || newSharedUserSetting) {
                 // The application on /system is newer than the application on /data.
                 // Simply remove the application on /data [keeping application data]
                 // and replace it with the version on /system.
+                // Also, if the sharedUserSetting of the application on /system is different
+                // from the sharedUserSetting on data, we should trust the sharedUserSetting
+                // on /system, even if the application version on /system is smaller than
+                // the version on /data.
                 logCriticalInfo(Log.WARN,
                         "System package enabled;"
                                 + " name: " + pkgSetting.getPackageName()
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index bb23d89d..02cf433 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -27,6 +27,7 @@
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PackageDeleteObserver;
@@ -1360,7 +1361,10 @@
                     PackageInstaller.STATUS_PENDING_USER_ACTION);
             fillIn.putExtra(Intent.EXTRA_INTENT, intent);
             try {
-                mTarget.sendIntent(mContext, 0, fillIn, null, null);
+                final BroadcastOptions options = BroadcastOptions.makeBasic();
+                options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+                mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/,
+                        null /* handler */, null /* requiredPermission */, options.toBundle());
             } catch (SendIntentException ignored) {
             }
         }
@@ -1385,7 +1389,10 @@
                     PackageManager.deleteStatusToString(returnCode, msg));
             fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
             try {
-                mTarget.sendIntent(mContext, 0, fillIn, null, null);
+                final BroadcastOptions options = BroadcastOptions.makeBasic();
+                options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+                mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/,
+                        null /* handler */, null /* requiredPermission */, options.toBundle());
             } catch (SendIntentException ignored) {
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 7c2e3ea..1823de8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -54,6 +54,7 @@
 import android.annotation.Nullable;
 import android.annotation.WorkerThread;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyEventLogger;
@@ -4274,7 +4275,10 @@
         fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION);
         fillIn.putExtra(Intent.EXTRA_INTENT, intent);
         try {
-            target.sendIntent(context, 0, fillIn, null, null);
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+            options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+            target.sendIntent(context, 0, fillIn, null /* onFinished */,
+                    null /* handler */, null /* requiredPermission */, options.toBundle());
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
@@ -4315,7 +4319,10 @@
             }
         }
         try {
-            target.sendIntent(context, 0, fillIn, null, null);
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+            options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+            target.sendIntent(context, 0, fillIn, null /* onFinished */,
+                    null /* handler */, null /* requiredPermission */, options.toBundle());
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
@@ -4349,7 +4356,10 @@
             intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready");
         }
         try {
-            target.sendIntent(context, 0, intent, null, null);
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+            options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+            target.sendIntent(context, 0, intent, null /* onFinished */,
+                    null /* handler */, null /* requiredPermission */, options.toBundle());
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8d2714c..4786037 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -53,6 +53,7 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.ApplicationPackageManager;
+import android.app.BroadcastOptions;
 import android.app.IActivityManager;
 import android.app.admin.IDevicePolicyManager;
 import android.app.admin.SecurityLog;
@@ -4798,7 +4799,11 @@
                 }
                 if (pi != null) {
                     try {
-                        pi.sendIntent(null, success ? 1 : 0, null, null, null);
+                        final BroadcastOptions options = BroadcastOptions.makeBasic();
+                        options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+                        pi.sendIntent(null, success ? 1 : 0, null /* intent */,
+                                null /* onFinished*/, null /* handler */,
+                                null /* requiredPermission */, options.toBundle());
                     } catch (SendIntentException e) {
                         Slog.w(TAG, e);
                     }
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 7ce7f7e..810fa5f 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -247,6 +247,9 @@
     private static final String MAX_NUM_COMPONENTS_ERR_MSG =
             "Total number of components has exceeded the maximum number: " + MAX_NUM_COMPONENTS;
 
+    /** The maximum permission name length. */
+    private static final int MAX_PERMISSION_NAME_LENGTH = 512;
+
     @IntDef(flag = true, prefix = { "PARSE_" }, value = {
             PARSE_CHATTY,
             PARSE_COLLECT_CERTIFICATES,
@@ -1275,6 +1278,11 @@
             // that may change.
             String name = sa.getNonResourceString(
                     R.styleable.AndroidManifestUsesPermission_name);
+            if (TextUtils.length(name) > MAX_PERMISSION_NAME_LENGTH) {
+                return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                        "The name in the <uses-permission> is greater than "
+                                + MAX_PERMISSION_NAME_LENGTH);
+            }
 
             int maxSdkVersion = 0;
             TypedValue val = sa.peekValue(
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
index 2d3ede0..f586126 100644
--- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
@@ -70,12 +70,14 @@
                 Log.i(TAG, "Operation cancelled for client " + mCallback.hashCode());
                 wrapCallback(mCallback::onCancel);
             } else if (e instanceof RkpProxyException) {
-                Log.e(TAG, "RKP error fetching key for client " + mCallback.hashCode(), e);
+                Log.e(TAG, "RKP error fetching key for client " + mCallback.hashCode() + ": "
+                        + e.getMessage());
                 RkpProxyException rkpException = (RkpProxyException) e;
                 wrapCallback(() -> mCallback.onError(toGetKeyError(rkpException),
                         e.getMessage()));
             } else {
-                Log.e(TAG, "Error fetching key for client " + mCallback.hashCode(), e);
+                Log.e(TAG, "Unknown error fetching key for client " + mCallback.hashCode() + ": "
+                        + e.getMessage());
                 wrapCallback(() -> mCallback.onError(IGetKeyCallback.ErrorCode.ERROR_UNKNOWN,
                         e.getMessage()));
             }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/OWNERS b/services/core/java/com/android/server/soundtrigger_middleware/OWNERS
index 01b2cb9..1e41886 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/OWNERS
+++ b/services/core/java/com/android/server/soundtrigger_middleware/OWNERS
@@ -1,2 +1 @@
-atneya@google.com
-elaurent@google.com
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b4b8cf9..3d8b293 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -147,7 +147,6 @@
 import android.security.metrics.KeystoreAtom;
 import android.security.metrics.KeystoreAtomPayload;
 import android.security.metrics.RkpErrorStats;
-import android.security.metrics.RkpPoolStats;
 import android.security.metrics.StorageStats;
 import android.stats.storage.StorageEnums;
 import android.telephony.ModemActivityInfo;
@@ -714,7 +713,6 @@
                             return pullInstalledIncrementalPackagesLocked(atomTag, data);
                         }
                     case FrameworkStatsLog.KEYSTORE2_STORAGE_STATS:
-                    case FrameworkStatsLog.RKP_POOL_STATS:
                     case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_GENERAL_INFO:
                     case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_AUTH_INFO:
                     case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_PURPOSE_AND_MODES_INFO:
@@ -918,7 +916,6 @@
         registerSettingsStats();
         registerInstalledIncrementalPackages();
         registerKeystoreStorageStats();
-        registerRkpPoolStats();
         registerKeystoreKeyCreationWithGeneralInfo();
         registerKeystoreKeyCreationWithAuthInfo();
         registerKeystoreKeyCreationWithPurposeModesInfo();
@@ -4137,14 +4134,6 @@
                 mStatsCallbackImpl);
     }
 
-    private void registerRkpPoolStats() {
-        mStatsManager.setPullAtomCallback(
-                FrameworkStatsLog.RKP_POOL_STATS,
-                null, // use default PullAtomMetadata values,
-                DIRECT_EXECUTOR,
-                mStatsCallbackImpl);
-    }
-
     private void registerKeystoreKeyCreationWithGeneralInfo() {
         mStatsManager.setPullAtomCallback(
                 FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_GENERAL_INFO,
@@ -4252,19 +4241,6 @@
         return StatsManager.PULL_SUCCESS;
     }
 
-    int parseRkpPoolStats(KeystoreAtom[] atoms, List<StatsEvent> pulledData) {
-        for (KeystoreAtom atomWrapper : atoms) {
-            if (atomWrapper.payload.getTag() != KeystoreAtomPayload.rkpPoolStats) {
-                return StatsManager.PULL_SKIP;
-            }
-            RkpPoolStats atom = atomWrapper.payload.getRkpPoolStats();
-            pulledData.add(FrameworkStatsLog.buildStatsEvent(
-                    FrameworkStatsLog.RKP_POOL_STATS, atom.security_level, atom.expiring,
-                    atom.unassigned, atom.attested, atom.total));
-        }
-        return StatsManager.PULL_SUCCESS;
-    }
-
     int parseKeystoreKeyCreationWithGeneralInfo(KeystoreAtom[] atoms, List<StatsEvent> pulledData) {
         for (KeystoreAtom atomWrapper : atoms) {
             if (atomWrapper.payload.getTag()
@@ -4397,8 +4373,6 @@
             switch (atomTag) {
                 case FrameworkStatsLog.KEYSTORE2_STORAGE_STATS:
                     return parseKeystoreStorageStats(atoms, pulledData);
-                case FrameworkStatsLog.RKP_POOL_STATS:
-                    return parseRkpPoolStats(atoms, pulledData);
                 case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_GENERAL_INFO:
                     return parseKeystoreKeyCreationWithGeneralInfo(atoms, pulledData);
                 case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_AUTH_INFO:
diff --git a/services/core/java/com/android/server/vcn/TEST_MAPPING b/services/core/java/com/android/server/vcn/TEST_MAPPING
new file mode 100644
index 0000000..5b04d88
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksVcnTests"
+    },
+    {
+      "name": "CtsVcnTestCases"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index 2141eba..7f129ea 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -171,6 +171,18 @@
             return false;
         }
 
+        for (Map.Entry<Integer, Integer> entry :
+                networkPriority.getCapabilitiesMatchCriteria().entrySet()) {
+            final int cap = entry.getKey();
+            final int matchCriteria = entry.getValue();
+
+            if (matchCriteria == MATCH_REQUIRED && !caps.hasCapability(cap)) {
+                return false;
+            } else if (matchCriteria == MATCH_FORBIDDEN && caps.hasCapability(cap)) {
+                return false;
+            }
+        }
+
         if (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST)) {
             return true;
         }
@@ -319,18 +331,6 @@
             return false;
         }
 
-        for (Map.Entry<Integer, Integer> entry :
-                networkPriority.getCapabilitiesMatchCriteria().entrySet()) {
-            final int cap = entry.getKey();
-            final int matchCriteria = entry.getValue();
-
-            if (matchCriteria == MATCH_REQUIRED && !caps.hasCapability(cap)) {
-                return false;
-            } else if (matchCriteria == MATCH_FORBIDDEN && caps.hasCapability(cap)) {
-                return false;
-            }
-        }
-
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index fd6c974..b160af6a 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -98,7 +98,7 @@
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
                 return mService.getRecentTasks().createRecentTaskInfo(task,
-                        false /* stripExtras */);
+                        false /* stripExtras */, true /* getTasksAllowed */);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 4860762..1fc061b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -976,7 +976,7 @@
                 continue;
             }
 
-            res.add(createRecentTaskInfo(task, true /* stripExtras */));
+            res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed));
         }
         return res;
     }
@@ -1895,7 +1895,8 @@
     /**
      * Creates a new RecentTaskInfo from a Task.
      */
-    ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) {
+    ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras,
+            boolean getTasksAllowed) {
         final ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
         // If the recent Task is detached, we consider it will be re-attached to the default
         // TaskDisplayArea because we currently only support recent overview in the default TDA.
@@ -1907,6 +1908,9 @@
         rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
         rti.persistentId = rti.taskId;
         rti.lastSnapshotData.set(tr.mLastTaskSnapshotData);
+        if (!getTasksAllowed) {
+            Task.trimIneffectiveInfo(tr, rti);
+        }
 
         // Fill in organized child task info for the task created by organizer.
         if (tr.mCreatedByOrganizer) {
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 33f019e..4e339f1 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -177,6 +177,10 @@
         }
         // Fill in some deprecated values
         rti.id = rti.taskId;
+
+        if (!mAllowed) {
+            Task.trimIneffectiveInfo(task, rti);
+        }
         return rti;
     }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 25957f1..e0e5ba8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3471,6 +3471,54 @@
         info.isSleeping = shouldSleepActivities();
     }
 
+    /**
+     * Removes the activity info if the activity belongs to a different uid, which is
+     * different from the app that hosts the task.
+     */
+    static void trimIneffectiveInfo(Task task, TaskInfo info) {
+        final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing,
+                false /* traverseTopToBottom */);
+        final int baseActivityUid =
+                baseActivity != null ? baseActivity.getUid() : task.effectiveUid;
+
+        if (info.topActivityInfo != null
+                && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) {
+            // Making a copy to prevent eliminating the info in the original ActivityRecord.
+            info.topActivityInfo = new ActivityInfo(info.topActivityInfo);
+            info.topActivityInfo.applicationInfo =
+                    new ApplicationInfo(info.topActivityInfo.applicationInfo);
+
+            // Strip the sensitive info.
+            info.topActivity = new ComponentName("", "");
+            info.topActivityInfo.packageName = "";
+            info.topActivityInfo.taskAffinity = "";
+            info.topActivityInfo.processName = "";
+            info.topActivityInfo.name = "";
+            info.topActivityInfo.parentActivityName = "";
+            info.topActivityInfo.targetActivity = "";
+            info.topActivityInfo.splitName = "";
+            info.topActivityInfo.applicationInfo.className = "";
+            info.topActivityInfo.applicationInfo.credentialProtectedDataDir = "";
+            info.topActivityInfo.applicationInfo.dataDir = "";
+            info.topActivityInfo.applicationInfo.deviceProtectedDataDir = "";
+            info.topActivityInfo.applicationInfo.manageSpaceActivityName = "";
+            info.topActivityInfo.applicationInfo.nativeLibraryDir = "";
+            info.topActivityInfo.applicationInfo.nativeLibraryRootDir = "";
+            info.topActivityInfo.applicationInfo.processName = "";
+            info.topActivityInfo.applicationInfo.publicSourceDir = "";
+            info.topActivityInfo.applicationInfo.scanPublicSourceDir = "";
+            info.topActivityInfo.applicationInfo.scanSourceDir = "";
+            info.topActivityInfo.applicationInfo.sourceDir = "";
+            info.topActivityInfo.applicationInfo.taskAffinity = "";
+            info.topActivityInfo.applicationInfo.name = "";
+            info.topActivityInfo.applicationInfo.packageName = "";
+        }
+
+        if (task.effectiveUid != baseActivityUid) {
+            info.baseActivity = new ComponentName("", "");
+        }
+    }
+
     @Nullable PictureInPictureParams getPictureInPictureParams() {
         final Task topTask = getTopMostTask();
         if (topTask == null) return null;
diff --git a/services/core/java/com/android/server/wm/TaskFpsCallbackController.java b/services/core/java/com/android/server/wm/TaskFpsCallbackController.java
index c099628..8c79875 100644
--- a/services/core/java/com/android/server/wm/TaskFpsCallbackController.java
+++ b/services/core/java/com/android/server/wm/TaskFpsCallbackController.java
@@ -26,8 +26,8 @@
 final class TaskFpsCallbackController {
 
     private final Context mContext;
-    private final HashMap<ITaskFpsCallback, Long> mTaskFpsCallbacks;
-    private final HashMap<ITaskFpsCallback, IBinder.DeathRecipient> mDeathRecipients;
+    private final HashMap<IBinder, Long> mTaskFpsCallbacks;
+    private final HashMap<IBinder, IBinder.DeathRecipient> mDeathRecipients;
 
     TaskFpsCallbackController(Context context) {
         mContext = context;
@@ -36,32 +36,42 @@
     }
 
     void registerListener(int taskId, ITaskFpsCallback callback) {
-        if (mTaskFpsCallbacks.containsKey(callback)) {
+        if (callback == null) {
+            return;
+        }
+
+        IBinder binder = callback.asBinder();
+        if (mTaskFpsCallbacks.containsKey(binder)) {
             return;
         }
 
         final long nativeListener = nativeRegister(callback, taskId);
-        mTaskFpsCallbacks.put(callback, nativeListener);
+        mTaskFpsCallbacks.put(binder, nativeListener);
 
         final IBinder.DeathRecipient deathRecipient = () -> unregisterListener(callback);
         try {
-            callback.asBinder().linkToDeath(deathRecipient, 0);
-            mDeathRecipients.put(callback, deathRecipient);
+            binder.linkToDeath(deathRecipient, 0);
+            mDeathRecipients.put(binder, deathRecipient);
         } catch (RemoteException e) {
             // ignore
         }
     }
 
     void unregisterListener(ITaskFpsCallback callback) {
-        if (!mTaskFpsCallbacks.containsKey(callback)) {
+        if (callback == null) {
             return;
         }
 
-        callback.asBinder().unlinkToDeath(mDeathRecipients.get(callback), 0);
-        mDeathRecipients.remove(callback);
+        IBinder binder = callback.asBinder();
+        if (!mTaskFpsCallbacks.containsKey(binder)) {
+            return;
+        }
 
-        nativeUnregister(mTaskFpsCallbacks.get(callback));
-        mTaskFpsCallbacks.remove(callback);
+        binder.unlinkToDeath(mDeathRecipients.get(binder), 0);
+        mDeathRecipients.remove(binder);
+
+        nativeUnregister(mTaskFpsCallbacks.get(binder));
+        mTaskFpsCallbacks.remove(binder);
     }
 
     private static native long nativeRegister(ITaskFpsCallback callback, int taskId);
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 2584b86..d9acf41 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -27,3 +27,4 @@
 per-file com_android_server_tv_* = file:/media/java/android/media/tv/OWNERS
 per-file com_android_server_vibrator_* = file:/services/core/java/com/android/server/vibrator/OWNERS
 per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com
+per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS
diff --git a/services/tests/PackageManagerServiceTests/TEST_MAPPING b/services/tests/PackageManagerServiceTests/TEST_MAPPING
index e98acb2..5d96af9 100644
--- a/services/tests/PackageManagerServiceTests/TEST_MAPPING
+++ b/services/tests/PackageManagerServiceTests/TEST_MAPPING
@@ -55,23 +55,10 @@
           // TODO(b/204133664)
           "exclude-filter": "com.android.server.pm.test.SdCardEjectionTests"
         },
-	{
-          // TODO(b/272575212)
-          "exclude-filter": "com.android.server.pm.test.SettingsTest#testWriteCorruptDataBinaryXml"
-	},
-	{
-          "exclude-filter": "com.android.server.pm.test.SettingsTest#testWriteCorruptDataTextXml"
-	},
-	{
-          "exclude-filter": "com.android.server.pm.test.SettingsTest#testWriteCorruptHeaderBinaryXml"
-	},
-	{
-          "exclude-filter": "com.android.server.pm.test.SettingsTest#testWriteCorruptHeaderTextXml"
-	},
-	{
+        {
           // TODO(b/272714903)
           "exclude-filter": "com.android.server.pm.test.OverlayPathsUninstallSystemUpdatesTest#verify"
-	}
+        }
       ]
     }
   ],
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
new file mode 100644
index 0000000..daa0211
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
@@ -0,0 +1,3 @@
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index d622a80..40d9948 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -103,6 +103,9 @@
     data: [
         ":JobTestApp",
         ":StubTestApp",
+        ":SimpleServiceTestApp1",
+        ":SimpleServiceTestApp2",
+        ":SimpleServiceTestApp3",
     ],
 
     java_resources: [
diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
index 428eaff..3ad24de 100644
--- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
@@ -17,12 +17,17 @@
 
 import com.android.server.audio.SpatializerHelper.SADeviceState;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
+import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
 import android.media.AudioSystem;
 import android.util.Log;
 
@@ -36,6 +41,7 @@
 import org.mockito.Mock;
 import org.mockito.Spy;
 
+import java.util.ArrayList;
 import java.util.List;
 
 @MediumTest
@@ -49,14 +55,35 @@
 
     @Mock private AudioService mMockAudioService;
     @Spy private AudioSystemAdapter mSpyAudioSystem;
+    @Mock private AudioSystemAdapter mMockAudioSystem;
 
     @Before
     public void setUp() throws Exception {
         mMockAudioService = mock(AudioService.class);
-        mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
+    }
 
-        mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem,
-                false /*headTrackingEnabledByDefault*/);
+    /**
+     * Initializes mSpatHelper, the SpatizerHelper instance under test, to use the mock or spy
+     * AudioSystemAdapter
+     * @param useSpyAudioSystem true to use the spy adapter, mSpyAudioSystem, or false to use
+     *                          the mock adapter, mMockAudioSystem.
+     */
+    private void setUpSpatHelper(boolean useSpyAudioSystem) {
+        final AudioSystemAdapter asAdapter;
+        if (useSpyAudioSystem) {
+            mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
+            asAdapter = mSpyAudioSystem;
+            mMockAudioSystem = null;
+        } else {
+            mSpyAudioSystem = null;
+            mMockAudioSystem = mock(NoOpAudioSystemAdapter.class);
+            asAdapter = mMockAudioSystem;
+        }
+        mSpatHelper = new SpatializerHelper(mMockAudioService, asAdapter,
+                true /*binauralEnabledDefault*/,
+                true /*transauralEnabledDefault*/,
+                false /*headTrackingEnabledDefault*/);
+
     }
 
     /**
@@ -66,6 +93,7 @@
      */
     @Test
     public void testSADeviceStateNullAddressCtor() throws Exception {
+        setUpSpatHelper(true /*useSpyAudioSystem*/);
         try {
             SADeviceState devState = new SADeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
             devState = new SADeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, null);
@@ -76,6 +104,7 @@
     @Test
     public void testSADeviceStateStringSerialization() throws Exception {
         Log.i(TAG, "starting testSADeviceStateStringSerialization");
+        setUpSpatHelper(true /*useSpyAudioSystem*/);
         final SADeviceState devState = new SADeviceState(
                 AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "bla");
         devState.mHasHeadTracker = false;
@@ -91,6 +120,7 @@
     @Test
     public void testSADeviceSettings() throws Exception {
         Log.i(TAG, "starting testSADeviceSettings");
+        setUpSpatHelper(true /*useSpyAudioSystem*/);
         final AudioDeviceAttributes dev1 =
                 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
         final AudioDeviceAttributes dev2 =
@@ -141,4 +171,34 @@
         Log.i(TAG, "device settingsRestored: " + settingsRestored);
         Assert.assertEquals(settings, settingsRestored);
     }
+
+    /**
+     * Test that null devices for routing do not break canBeSpatialized
+     * @throws Exception
+     */
+    @Test
+    public void testNoRoutingCanBeSpatialized() throws Exception {
+        Log.i(TAG, "Starting testNoRoutingCanBeSpatialized");
+        setUpSpatHelper(false /*useSpyAudioSystem*/);
+        mSpatHelper.forceStateForTest(SpatializerHelper.STATE_ENABLED_AVAILABLE);
+
+        final ArrayList<AudioDeviceAttributes> emptyList = new ArrayList<>(0);
+        final ArrayList<AudioDeviceAttributes> listWithNull = new ArrayList<>(1);
+        listWithNull.add(null);
+        final AudioAttributes media = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA).build();
+        final AudioFormat spatialFormat = new AudioFormat.Builder()
+                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1).build();
+
+        when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
+                .thenReturn(emptyList);
+        Assert.assertFalse("can be spatialized on empty routing",
+                mSpatHelper.canBeSpatialized(media, spatialFormat));
+
+        when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
+                .thenReturn(listWithNull);
+        Assert.assertFalse("can be spatialized on null routing",
+                mSpatHelper.canBeSpatialized(media, spatialFormat));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS
index 33385af..1e41886 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS
@@ -1 +1 @@
-include /media/aidl/android/media/soundtrigger_middleware/OWNERS
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index adf694c..db6ac0b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -30,6 +30,7 @@
 import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.os.Process.NOBODY_UID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -1220,20 +1221,35 @@
 
     @Test
     public void testCreateRecentTaskInfo_detachedTask() {
-        final Task task = createTaskBuilder(".Task").setCreateActivity(true).build();
+        final Task task = createTaskBuilder(".Task").build();
+        final ComponentName componentName = getUniqueComponentName();
+        new ActivityBuilder(mSupervisor.mService)
+                .setTask(task)
+                .setUid(NOBODY_UID)
+                .setComponent(componentName)
+                .build();
         final TaskDisplayArea tda = task.getDisplayArea();
 
         assertTrue(task.isAttached());
         assertTrue(task.supportsMultiWindow());
 
-        RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true);
+        RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertTrue(info.supportsMultiWindow);
 
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                false /* getTasksAllowed */);
+
+        assertFalse(info.topActivity.equals(componentName));
+        assertFalse(info.topActivityInfo.packageName.equals(componentName.getPackageName()));
+        assertFalse(info.baseActivity.equals(componentName));
+
         // The task can be put in split screen even if it is not attached now.
         task.removeImmediately();
 
-        info = mRecentTasks.createRecentTaskInfo(task, true);
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertTrue(info.supportsMultiWindow);
 
@@ -1242,7 +1258,8 @@
         doReturn(false).when(tda).supportsNonResizableMultiWindow();
         doReturn(false).when(task).isResizeable();
 
-        info = mRecentTasks.createRecentTaskInfo(task, true);
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertFalse(info.supportsMultiWindow);
 
@@ -1250,7 +1267,8 @@
         // the device supports it.
         doReturn(true).when(tda).supportsNonResizableMultiWindow();
 
-        info = mRecentTasks.createRecentTaskInfo(task, true);
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertTrue(info.supportsMultiWindow);
     }
diff --git a/services/voiceinteraction/OWNERS b/services/voiceinteraction/OWNERS
index ef1061b..40e8d26 100644
--- a/services/voiceinteraction/OWNERS
+++ b/services/voiceinteraction/OWNERS
@@ -1 +1,2 @@
 include /core/java/android/service/voice/OWNERS
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS b/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS
index 01b2cb9..1e41886 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS
@@ -1,2 +1 @@
-atneya@google.com
-elaurent@google.com
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index fdf69430..f90eabc 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -18,6 +18,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.Context;
@@ -822,4 +823,35 @@
         }
         return Integer.MAX_VALUE;
     }
+
+    /**
+     * Check if calling user is associated with the given subscription.
+     * @param context Context
+     * @param subId subscription ID
+     * @param callerUserHandle caller user handle
+     * @return  false if user is not associated with the subscription.
+     */
+    public static boolean checkSubscriptionAssociatedWithUser(@NonNull Context context, int subId,
+            @NonNull UserHandle callerUserHandle) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            // No subscription on device, return true.
+            return true;
+        }
+
+        SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            if ((subManager != null) &&
+                    (!subManager.isSubscriptionAssociatedWithUser(subId, callerUserHandle))) {
+                // If subId is not associated with calling user, return false.
+                Log.e(LOG_TAG,"User[User ID:" + callerUserHandle.getIdentifier()
+                        + "] is not associated with Subscription ID:" + subId);
+                return false;
+
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return true;
+    }
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index f570f87..c90b94d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3708,13 +3708,12 @@
      * NR_SA - NR SA is unmetered for sub-6 frequencies
      * NR_SA_MMWAVE - NR SA is unmetered for mmwave frequencies
      *
-     * Note that this config only applies if an unmetered SubscriptionPlan is set via
-     * {@link SubscriptionManager#setSubscriptionPlans(int, List)} or an unmetered override is set
+     * Note that this config only applies if an unmetered SubscriptionPlan is set via {@link
+     * SubscriptionManager#setSubscriptionPlans(int, List, long)} or an unmetered override is set
      * via {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, int[], long)}
      * or {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, long)}.
      * If neither SubscriptionPlans nor an override are set, then no network types can be unmetered
      * regardless of the value of this config.
-     * TODO: remove other unmetered keys and replace with this
      * @hide
      */
     public static final String KEY_UNMETERED_NETWORK_TYPES_STRING_ARRAY =
@@ -3729,73 +3728,18 @@
      * NR_SA - NR SA is unmetered when roaming for sub-6 frequencies
      * NR_SA_MMWAVE - NR SA is unmetered when roaming for mmwave frequencies
      *
-     * Note that this config only applies if an unmetered SubscriptionPlan is set via
-     * {@link SubscriptionManager#setSubscriptionPlans(int, List)} or an unmetered override is set
+     * Note that this config only applies if an unmetered SubscriptionPlan is set via {@link
+     * SubscriptionManager#setSubscriptionPlans(int, List, long)} or an unmetered override is set
      * via {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, int[], long)}
      * or {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, long)}.
      * If neither SubscriptionPlans nor an override are set, then no network types can be unmetered
      * when roaming regardless of the value of this config.
-     * TODO: remove KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL and replace with this
      * @hide
      */
     public static final String KEY_ROAMING_UNMETERED_NETWORK_TYPES_STRING_ARRAY =
             "roaming_unmetered_network_types_string_array";
 
     /**
-     * Whether NR (non-standalone) should be unmetered for all frequencies.
-     * If either {@link #KEY_UNMETERED_NR_NSA_MMWAVE_BOOL} or
-     * {@link #KEY_UNMETERED_NR_NSA_SUB6_BOOL} are true, then this value will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_NSA_BOOL = "unmetered_nr_nsa_bool";
-
-    /**
-     * Whether NR (non-standalone) frequencies above 6GHz (millimeter wave) should be unmetered.
-     * If this is true, then the value for {@link #KEY_UNMETERED_NR_NSA_BOOL} will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_NSA_MMWAVE_BOOL = "unmetered_nr_nsa_mmwave_bool";
-
-    /**
-     * Whether NR (non-standalone) frequencies below 6GHz (sub6) should be unmetered.
-     * If this is true, then the value for {@link #KEY_UNMETERED_NR_NSA_BOOL} will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_NSA_SUB6_BOOL = "unmetered_nr_nsa_sub6_bool";
-
-    /**
-     * Whether NR (non-standalone) should be unmetered when the device is roaming.
-     * If false, then the values for {@link #KEY_UNMETERED_NR_NSA_BOOL},
-     * {@link #KEY_UNMETERED_NR_NSA_MMWAVE_BOOL}, {@link #KEY_UNMETERED_NR_NSA_SUB6_BOOL},
-     * and unmetered {@link SubscriptionPlan} will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL =
-            "unmetered_nr_nsa_when_roaming_bool";
-
-    /**
-     * Whether NR (standalone) should be unmetered for all frequencies.
-     * If either {@link #KEY_UNMETERED_NR_SA_MMWAVE_BOOL} or
-     * {@link #KEY_UNMETERED_NR_SA_SUB6_BOOL} are true, then this value will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_SA_BOOL = "unmetered_nr_sa_bool";
-
-    /**
-     * Whether NR (standalone) frequencies above 6GHz (millimeter wave) should be unmetered.
-     * If this is true, then the value for {@link #KEY_UNMETERED_NR_SA_BOOL} will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_SA_MMWAVE_BOOL = "unmetered_nr_sa_mmwave_bool";
-
-    /**
-     * Whether NR (standalone) frequencies below 6GHz (sub6) should be unmetered.
-     * If this is true, then the value for {@link #KEY_UNMETERED_NR_SA_BOOL} will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_SA_SUB6_BOOL = "unmetered_nr_sa_sub6_bool";
-
-    /**
      * Support ASCII 7-BIT encoding for long SMS. This carrier config is used to enable
      * this feature.
      * @hide
@@ -4478,6 +4422,57 @@
             "data_stall_recovery_should_skip_bool_array";
 
     /**
+     * String array containing the list of names for service numbers provided by carriers. This key
+     * should be used with {@link #KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY}. The names provided in
+     * this array will be mapped 1:1 with the numbers provided in the {@link
+     * #KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY} array.
+     *
+     * <p>The data would be considered valid if and only if:
+     *
+     * <ul>
+     *   <li>The number of items in both the arrays are equal
+     *   <li>The data added to the {@link #KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY} array is valid.
+     *       See {@link #KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY} for more information.
+     * </ul>
+     *
+     * <p>Example:
+     *
+     * <pre><code>
+     * <string-array name="carrier_service_name_array" num="2">
+     *   <item value="Police"/>
+     *   <item value="Ambulance"/>
+     * </string-array>
+     * </code></pre>
+     */
+    public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array";
+
+    /**
+     * String array containing the list of service numbers provided by carriers. This key should be
+     * used with {@link #KEY_CARRIER_SERVICE_NAME_STRING_ARRAY}. The numbers provided in this array
+     * will be mapped 1:1 with the names provided in the {@link
+     * #KEY_CARRIER_SERVICE_NAME_STRING_ARRAY} array.
+     *
+     * <p>The data would be considered valid if and only if:
+     *
+     * <ul>
+     *   <li>The number of items in both the arrays are equal
+     *   <li>The item added in this key follows a specific format. Either it should be all numbers,
+     *       or "+" followed by all numbers.
+     * </ul>
+     *
+     * <p>Example:
+     *
+     * <pre><code>
+     * <string-array name="carrier_service_number_array" num="2">
+     *   <item value="123"/>
+     *   <item value="+343"/>
+     * </string-array>
+     * </code></pre>
+     */
+    public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY =
+        "carrier_service_number_array";
+
+    /**
      * Configs used by ImsServiceEntitlement.
      */
     public static final class ImsServiceEntitlement {
@@ -4833,11 +4828,32 @@
      * The max acceptable value of this config is 24 hours.
      *
      * @hide
+     * @deprecated Use {@link #KEY_DATA_SWITCH_VALIDATION_MIN_INTERVAL_MILLIS_LONG} instead.
      */
+    @Deprecated
     public static final String KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG =
             "data_switch_validation_min_gap_long";
 
     /**
+     * Data switch validation minimal interval, in milliseconds.
+     *
+     * If a connection to the default (Internet) PDN for the current subscription is validated on
+     * a given operator within a given tracking area, re-validations to that matching operator will
+     * be skipped if they would occur within the specified interval. Instead, the connection will
+     * automatically considered validated.
+     *
+     * If the network was validated within the interval but the latest validation result was false,
+     * the validation will not be skipped. If not set or set to 0, validation will not be skipped.
+     *
+     * The valid range of value is between 0 millisecond and 24 hours, inclusive in both sides. The
+     * default value is 24 hours.
+     *
+     * @see android.net.NetworkCapabilities#NET_CAPABILITY_VALIDATED
+     */
+    public static final String KEY_DATA_SWITCH_VALIDATION_MIN_INTERVAL_MILLIS_LONG =
+            KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG;
+
+    /**
      * A boolean property indicating whether this subscription should be managed as an opportunistic
      * subscription.
      *
@@ -9183,13 +9199,6 @@
         sDefaults.putStringArray(KEY_UNMETERED_NETWORK_TYPES_STRING_ARRAY, new String[] {
                 "NR_NSA", "NR_NSA_MMWAVE", "NR_SA", "NR_SA_MMWAVE"});
         sDefaults.putStringArray(KEY_ROAMING_UNMETERED_NETWORK_TYPES_STRING_ARRAY, new String[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);
-        sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL, false);
-        sDefaults.putBoolean(KEY_UNMETERED_NR_SA_BOOL, false);
-        sDefaults.putBoolean(KEY_UNMETERED_NR_SA_MMWAVE_BOOL, false);
-        sDefaults.putBoolean(KEY_UNMETERED_NR_SA_SUB6_BOOL, false);
         sDefaults.putBoolean(KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_WIFI_CALLING_ICON_IN_STATUS_BAR_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL, false);
@@ -9284,7 +9293,8 @@
         sDefaults.putInt(KEY_GBA_UA_TLS_CIPHER_SUITE_INT, TlsParams.TLS_NULL_WITH_NULL_NULL);
 
         sDefaults.putBoolean(KEY_SHOW_FORWARDED_NUMBER_BOOL, false);
-        sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG, TimeUnit.DAYS.toMillis(1));
+        sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_MIN_INTERVAL_MILLIS_LONG,
+                TimeUnit.DAYS.toMillis(1));
         sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY,
                 new String[0]);
         sDefaults.putStringArray(KEY_APN_PRIORITY_STRING_ARRAY, new String[] {
@@ -9350,6 +9360,8 @@
                 new long[] {180000, 180000, 180000, 180000});
         sDefaults.putBooleanArray(KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_BOOL_ARRAY,
                 new boolean[] {false, false, true, false, false});
+        sDefaults.putStringArray(KEY_CARRIER_SERVICE_NAME_STRING_ARRAY, new String[0]);
+        sDefaults.putStringArray(KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY, new String[0]);
     }
 
     /**
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 1d6798b..f1f13bc 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -184,10 +184,11 @@
     private final int mTransportType;
 
     /**
-     * The initial registration state
+     * The true registration state of network, This is not affected by any carrier config or
+     * resource overlay.
      */
     @RegistrationState
-    private final int mInitialRegistrationState;
+    private final int mNetworkRegistrationState;
 
     /**
      * The registration state that might have been overridden by config
@@ -264,7 +265,7 @@
         mDomain = domain;
         mTransportType = transportType;
         mRegistrationState = registrationState;
-        mInitialRegistrationState = registrationState;
+        mNetworkRegistrationState = registrationState;
         mRoamingType = (registrationState == REGISTRATION_STATE_ROAMING)
                 ? ServiceState.ROAMING_TYPE_UNKNOWN : ServiceState.ROAMING_TYPE_NOT_ROAMING;
         setAccessNetworkTechnology(accessNetworkTechnology);
@@ -320,7 +321,7 @@
         mDomain = source.readInt();
         mTransportType = source.readInt();
         mRegistrationState = source.readInt();
-        mInitialRegistrationState = source.readInt();
+        mNetworkRegistrationState = source.readInt();
         mRoamingType = source.readInt();
         mAccessNetworkTechnology = source.readInt();
         mRejectCause = source.readInt();
@@ -347,7 +348,7 @@
         mDomain = nri.mDomain;
         mTransportType = nri.mTransportType;
         mRegistrationState = nri.mRegistrationState;
-        mInitialRegistrationState = nri.mInitialRegistrationState;
+        mNetworkRegistrationState = nri.mNetworkRegistrationState;
         mRoamingType = nri.mRoamingType;
         mAccessNetworkTechnology = nri.mAccessNetworkTechnology;
         mIsUsingCarrierAggregation = nri.mIsUsingCarrierAggregation;
@@ -400,40 +401,72 @@
     }
 
     /**
-     * @return The registration state.
+     * @return The registration state. Note this value can be affected by the carrier config
+     * override.
      *
+     * @deprecated Use {@link #getNetworkRegistrationState}, which is not affected by any carrier
+     * config or resource overlay, instead.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public @RegistrationState int getRegistrationState() {
         return mRegistrationState;
     }
 
     /**
-     * @return The initial registration state.
+     * @return The true registration state of network. (This value is not affected by any carrier
+     * config or resource overlay override).
      *
      * @hide
      */
-    public @RegistrationState int getInitialRegistrationState() {
-        return mInitialRegistrationState;
+    @SystemApi
+    public @RegistrationState int getNetworkRegistrationState() {
+        return mNetworkRegistrationState;
     }
 
     /**
-     * @return {@code true} if registered on roaming or home network, {@code false} otherwise.
+     * @return {@code true} if registered on roaming or home network. Note this value can be
+     * affected by the carrier config override.
+     *
+     * @deprecated Use {@link #isNetworkRegistered}, which is not affected by any carrier config or
+     * resource overlay, instead.
      */
+    @Deprecated
     public boolean isRegistered() {
         return mRegistrationState == REGISTRATION_STATE_HOME
                 || mRegistrationState == REGISTRATION_STATE_ROAMING;
     }
 
     /**
-     * @return {@code true} if searching for service, {@code false} otherwise.
+     * @return {@code true} if registered on roaming or home network, {@code false} otherwise. (This
+     * value is not affected by any carrier config or resource overlay override).
      */
+    public boolean isNetworkRegistered() {
+        return mNetworkRegistrationState == REGISTRATION_STATE_HOME
+                || mNetworkRegistrationState == REGISTRATION_STATE_ROAMING;
+    }
+
+    /**
+     * @return {@code true} if searching for service, {@code false} otherwise.
+     *
+     * @deprecated Use {@link #isNetworkRegistered}, which is not affected by any carrier config or
+     * resource overlay, instead.
+     */
+    @Deprecated
     public boolean isSearching() {
         return mRegistrationState == REGISTRATION_STATE_NOT_REGISTERED_SEARCHING;
     }
 
     /**
+     * @return {@code true} if searching for service, {@code false} otherwise. (This value is not
+     * affected by any carrier config or resource overlay override).
+     */
+    public boolean isNetworkSearching() {
+        return mNetworkRegistrationState == REGISTRATION_STATE_NOT_REGISTERED_SEARCHING;
+    }
+
+    /**
      * Get the PLMN-ID for this Network Registration, also known as the RPLMN.
      *
      * <p>If the device is registered, this will return the registered PLMN-ID. If registration
@@ -450,13 +483,25 @@
     }
 
     /**
-     * @return {@code true} if registered on roaming network, {@code false} otherwise.
+     * @return {@code true} if registered on roaming network overridden by config. Note this value
+     * can be affected by the carrier config override.
+     *
+     * @deprecated Use {@link TelephonyDisplayInfo#isRoaming} instead.
      */
+    @Deprecated
     public boolean isRoaming() {
         return mRoamingType != ServiceState.ROAMING_TYPE_NOT_ROAMING;
     }
 
     /**
+     * @return {@code true} if registered on roaming network. (This value is not affected by any
+     * carrier config or resource overlay override).
+     */
+    public boolean isNetworkRoaming() {
+        return mNetworkRegistrationState == REGISTRATION_STATE_ROAMING;
+    }
+
+    /**
      * @hide
      * @return {@code true} if in service.
      */
@@ -486,7 +531,8 @@
     }
 
     /**
-     * @return the current network roaming type.
+     * @return the current network roaming type. Note that this value can be possibly overridden by
+     * the carrier config or resource overlay.
      * @hide
      */
     @SystemApi
@@ -666,8 +712,8 @@
                 .append(" transportType=").append(
                         AccessNetworkConstants.transportTypeToString(mTransportType))
                 .append(" registrationState=").append(registrationStateToString(mRegistrationState))
-                .append(" mInitialRegistrationState=")
-                .append(registrationStateToString(mInitialRegistrationState))
+                .append(" networkRegistrationState=")
+                .append(registrationStateToString(mNetworkRegistrationState))
                 .append(" roamingType=").append(ServiceState.roamingTypeToString(mRoamingType))
                 .append(" accessNetworkTechnology=")
                 .append(TelephonyManager.getNetworkTypeName(mAccessNetworkTechnology))
@@ -688,7 +734,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mDomain, mTransportType, mRegistrationState, mInitialRegistrationState,
+        return Objects.hash(mDomain, mTransportType, mRegistrationState, mNetworkRegistrationState,
                 mRoamingType, mAccessNetworkTechnology, mRejectCause, mEmergencyOnly,
                 mAvailableServices, mCellIdentity, mVoiceSpecificInfo, mDataSpecificInfo, mNrState,
                 mRplmn, mIsUsingCarrierAggregation);
@@ -706,7 +752,7 @@
         return mDomain == other.mDomain
                 && mTransportType == other.mTransportType
                 && mRegistrationState == other.mRegistrationState
-                && mInitialRegistrationState == other.mInitialRegistrationState
+                && mNetworkRegistrationState == other.mNetworkRegistrationState
                 && mRoamingType == other.mRoamingType
                 && mAccessNetworkTechnology == other.mAccessNetworkTechnology
                 && mRejectCause == other.mRejectCause
@@ -729,7 +775,7 @@
         dest.writeInt(mDomain);
         dest.writeInt(mTransportType);
         dest.writeInt(mRegistrationState);
-        dest.writeInt(mInitialRegistrationState);
+        dest.writeInt(mNetworkRegistrationState);
         dest.writeInt(mRoamingType);
         dest.writeInt(mAccessNetworkTechnology);
         dest.writeInt(mRejectCause);
@@ -826,7 +872,7 @@
         private int mTransportType;
 
         @RegistrationState
-        private int mInitialRegistrationState;
+        private int mNetworkRegistrationState;
 
         @NetworkType
         private int mAccessNetworkTechnology;
@@ -856,6 +902,31 @@
         public Builder() {}
 
         /**
+         * Builder from the existing {@link NetworkRegistrationInfo}.
+         *
+         * @param nri The network registration info object.
+         * @hide
+         */
+        public Builder(@NonNull NetworkRegistrationInfo nri) {
+            mDomain = nri.mDomain;
+            mTransportType = nri.mTransportType;
+            mNetworkRegistrationState = nri.mNetworkRegistrationState;
+            mAccessNetworkTechnology = nri.mAccessNetworkTechnology;
+            mRejectCause = nri.mRejectCause;
+            mEmergencyOnly = nri.mEmergencyOnly;
+            mAvailableServices = new ArrayList<>(nri.mAvailableServices);
+            mCellIdentity = nri.mCellIdentity;
+            if (nri.mDataSpecificInfo != null) {
+                mDataSpecificRegistrationInfo = new DataSpecificRegistrationInfo(
+                        nri.mDataSpecificInfo);
+            }
+            if (nri.mVoiceSpecificInfo != null) {
+                mVoiceSpecificRegistrationInfo = new VoiceSpecificRegistrationInfo(
+                        nri.mVoiceSpecificInfo);
+            }
+        }
+
+        /**
          * Set the network domain.
          *
          * @param domain Network domain.
@@ -887,7 +958,7 @@
          * @return The same instance of the builder.
          */
         public @NonNull Builder setRegistrationState(@RegistrationState int registrationState) {
-            mInitialRegistrationState = registrationState;
+            mNetworkRegistrationState = registrationState;
             return this;
         }
 
@@ -1006,7 +1077,7 @@
          */
         @SystemApi
         public @NonNull NetworkRegistrationInfo build() {
-            return new NetworkRegistrationInfo(mDomain, mTransportType, mInitialRegistrationState,
+            return new NetworkRegistrationInfo(mDomain, mTransportType, mNetworkRegistrationState,
                     mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices,
                     mCellIdentity, mRplmn, mVoiceSpecificRegistrationInfo,
                     mDataSpecificRegistrationInfo);
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index ac74016..03e019d 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -631,11 +631,17 @@
     }
 
     /**
-     * Get current roaming indicator of phone
+     * Get current roaming indicator of phone. This roaming state could be overridden by the carrier
+     * config.
      * (note: not just decoding from TS 27.007 7.2)
-     *
+     * @see TelephonyDisplayInfo#isRoaming() for visualization purpose.
      * @return true if TS 27.007 7.2 roaming is true
      *              and ONS is different from SPN
+     * @see CarrierConfigManager#KEY_FORCE_HOME_NETWORK_BOOL
+     * @see CarrierConfigManager#KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+     * @see CarrierConfigManager#KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+     * @see CarrierConfigManager#KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY
+     * @see CarrierConfigManager#KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY
      */
     public boolean getRoaming() {
         return getVoiceRoaming() || getDataRoaming();
@@ -650,8 +656,9 @@
     public boolean getVoiceRoaming() {
         return getVoiceRoamingType() != ROAMING_TYPE_NOT_ROAMING;
     }
+
     /**
-     * Get current voice network roaming type
+     * Get current voice roaming type. This roaming type could be overridden by the carrier config.
      * @return roaming type
      * @hide
      */
@@ -701,7 +708,7 @@
     }
 
     /**
-     * Get current data network roaming type
+     * Get current data roaming type. This roaming type could be overridden by the carrier config.
      * @return roaming type
      * @hide
      */
@@ -1207,8 +1214,13 @@
 
     /**
      * Initialize the service state. Set everything to the default value.
+     *
+     * @param legacyMode {@code true} if the device is on IWLAN legacy mode, where IWLAN is
+     * considered as a RAT on WWAN {@link NetworkRegistrationInfo}. {@code false} if the device
+     * is on AP-assisted mode, where IWLAN should be reported through WLAN.
+     * {@link NetworkRegistrationInfo}.
      */
-    private void init() {
+    private void init(boolean legacyMode) {
         if (DBG) Rlog.d(LOG_TAG, "init");
         mVoiceRegState = STATE_OUT_OF_SERVICE;
         mDataRegState = STATE_OUT_OF_SERVICE;
@@ -1240,11 +1252,13 @@
                     .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                     .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN)
                     .build());
-            addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                    .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                    .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
-                    .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN)
-                    .build());
+            if (!legacyMode) {
+                addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+                        .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
+                        .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN)
+                        .build());
+            }
         }
         mOperatorAlphaLongRaw = null;
         mOperatorAlphaShortRaw = null;
@@ -1253,11 +1267,11 @@
     }
 
     public void setStateOutOfService() {
-        init();
+        init(true);
     }
 
     public void setStateOff() {
-        init();
+        init(true);
         mVoiceRegState = STATE_POWER_OFF;
         mDataRegState = STATE_POWER_OFF;
     }
@@ -1265,11 +1279,14 @@
     /**
      * Set the service state to out-of-service
      *
+     * @param legacyMode {@code true} if the device is on IWLAN legacy mode, where IWLAN is
+     * considered as a RAT on WWAN {@link NetworkRegistrationInfo}. {@code false} if the device
+     * is on AP-assisted mode, where IWLAN should be reported through WLAN.
      * @param powerOff {@code true} if this is a power off case (i.e. Airplane mode on).
      * @hide
      */
-    public void setOutOfService(boolean powerOff) {
-        init();
+    public void setOutOfService(boolean legacyMode, boolean powerOff) {
+        init(legacyMode);
         if (powerOff) {
             mVoiceRegState = STATE_POWER_OFF;
             mDataRegState = STATE_POWER_OFF;
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index d670e55..1cf2969 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2245,6 +2245,7 @@
             RESULT_SMS_SEND_RETRY_FAILED,
             RESULT_REMOTE_EXCEPTION,
             RESULT_NO_DEFAULT_SMS_APP,
+            RESULT_USER_NOT_ALLOWED,
             RESULT_RIL_RADIO_NOT_AVAILABLE,
             RESULT_RIL_SMS_SEND_FAIL_RETRY,
             RESULT_RIL_NETWORK_REJECT,
@@ -2425,6 +2426,13 @@
      */
     public static final int RESULT_NO_DEFAULT_SMS_APP = 32;
 
+    /**
+     * User is not associated with the subscription.
+     * TODO(b/263279115): Make this error code public.
+     * @hide
+     */
+    public static final int RESULT_USER_NOT_ALLOWED = 33;
+
     // Radio Error results
 
     /**
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 0cfd96e..45c092e 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -160,6 +160,10 @@
     private static final String CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY =
             "cache_key.telephony.subscription_manager_service";
 
+    /** The temporarily cache key to indicate whether subscription manager service is enabled. */
+    private static final String CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_ENABLED_PROPERTY =
+            "cache_key.telephony.subscription_manager_service_enabled";
+
     /** @hide */
     public static final String GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME = "getSimSpecificSettings";
 
@@ -317,6 +321,12 @@
                     CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
                     INVALID_PHONE_INDEX);
 
+    //TODO: Removed before U AOSP public release.
+    private static VoidPropertyInvalidatedCache<Boolean> sIsSubscriptionManagerServiceEnabled =
+            new VoidPropertyInvalidatedCache<>(ISub::isSubscriptionManagerServiceEnabled,
+                    CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_ENABLED_PROPERTY,
+                    false);
+
     /**
      * Generates a content {@link Uri} used to receive updates on simInfo change
      * on the given subscriptionId
@@ -1116,6 +1126,14 @@
      */
     public static final String USER_HANDLE = SimInfo.COLUMN_USER_HANDLE;
 
+    /**
+     * TelephonyProvider column name for satellite enabled.
+     * By default, it's disabled.
+     * <P>Type: INTEGER (int)</P>
+     * @hide
+     */
+    public static final String SATELLITE_ENABLED = SimInfo.COLUMN_SATELLITE_ENABLED;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"USAGE_SETTING_"},
@@ -1327,8 +1345,6 @@
 
     private final Context mContext;
 
-    private static boolean sIsSubscriptionManagerServiceEnabled = false;
-
     // Cache of Resource that has been created in getResourcesForSubId. Key is a Pair containing
     // the Context and subId.
     private static final Map<Pair<Context, Integer>, Resources> sResourcesCache =
@@ -1414,9 +1430,6 @@
     public SubscriptionManager(Context context) {
         if (DBG) logd("SubscriptionManager created");
         mContext = context;
-
-        sIsSubscriptionManagerServiceEnabled = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_using_subscription_manager_service);
     }
 
     /**
@@ -1425,8 +1438,9 @@
      *
      * @hide
      */
+    //TODO: Removed before U AOSP public release.
     public static boolean isSubscriptionManagerServiceEnabled() {
-        return sIsSubscriptionManagerServiceEnabled;
+        return sIsSubscriptionManagerServiceEnabled.query(null);
     }
 
     private NetworkPolicyManager getNetworkPolicyManager() {
@@ -1707,31 +1721,30 @@
     }
 
     /**
-     * Get all subscription info records from SIMs that are inserted now or were inserted before.
+     * Get all subscription info records from SIMs that are inserted now or previously inserted.
      *
      * <p>
      * If the caller does not have {@link Manifest.permission#READ_PHONE_NUMBERS} permission,
      * {@link SubscriptionInfo#getNumber()} will return empty string.
      * If the caller does not have {@link Manifest.permission#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER},
-     * {@link SubscriptionInfo#getIccId()} and {@link SubscriptionInfo#getCardString()} will return
-     * empty string, and {@link SubscriptionInfo#getGroupUuid()} will return {@code null}.
+     * {@link SubscriptionInfo#getIccId()} will return an empty string, and
+     * {@link SubscriptionInfo#getGroupUuid()} will return {@code null}.
      *
      * <p>
-     * The carrier app will always have full {@link SubscriptionInfo} for the subscriptions
-     * that it has carrier privilege.
+     * The carrier app will only get the list of subscriptions that it has carrier privilege on,
+     * but will have non-stripped {@link SubscriptionInfo} in the list.
      *
      * @return List of all {@link SubscriptionInfo} records from SIMs that are inserted or
-     * inserted before. Sorted by {@link SubscriptionInfo#getSimSlotIndex()}, then
+     * previously inserted. Sorted by {@link SubscriptionInfo#getSimSlotIndex()}, then
      * {@link SubscriptionInfo#getSubscriptionId()}.
      *
-     * @hide
+     * @throws SecurityException if callers do not hold the required permission.
      */
+    @NonNull
     @RequiresPermission(anyOf = {
             Manifest.permission.READ_PHONE_STATE,
-            Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             "carrier privileges",
     })
-    @NonNull
     public List<SubscriptionInfo> getAllSubscriptionInfoList() {
         List<SubscriptionInfo> result = null;
         try {
@@ -2205,13 +2218,23 @@
     }
 
     /**
-     * Get an array of Subscription Ids for specified slot Index.
-     * @param slotIndex the slot index.
-     * @return subscription Ids or null if the given slot Index is not valid or there are no active
-     * subscriptions in the slot.
+     * Get an array of subscription ids for specified logical SIM slot Index.
+     *
+     * @param slotIndex The logical SIM slot index.
+     *
+     * @return subscription Ids or {@code null} if the given slot index is not valid or there are
+     * no active subscription in the slot. In the implementation today, there will be no more
+     * than one subscriptions per logical SIM slot.
+     *
+     * @deprecated Use {@link #getSubscriptionId(int)} instead.
      */
+    @Deprecated
     @Nullable
     public int[] getSubscriptionIds(int slotIndex) {
+        int subId = getSubscriptionId(slotIndex);
+        if (!isValidSubscriptionId(subId)) {
+            return null;
+        }
         return new int[]{getSubscriptionId(slotIndex)};
     }
 
@@ -2238,12 +2261,10 @@
     }
 
     /**
-     * Get the subscription id for specified slot index.
+     * Get the subscription id for specified logical SIM slot index.
      *
-     * @param slotIndex Logical SIM slot index.
+     * @param slotIndex The logical SIM slot index.
      * @return The subscription id. {@link #INVALID_SUBSCRIPTION_ID} if SIM is absent.
-     *
-     * @hide
      */
     public static int getSubscriptionId(int slotIndex) {
         if (!isValidSlotIndex(slotIndex)) {
@@ -3932,6 +3953,13 @@
         PropertyInvalidatedCache.invalidateCache(CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY);
     }
 
+    /** @hide */
+    //TODO: Removed before U AOSP public release.
+    public static void invalidateSubscriptionManagerServiceEnabledCaches() {
+        PropertyInvalidatedCache.invalidateCache(
+                CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_ENABLED_PROPERTY);
+    }
+
     /**
      * Allows a test process to disable client-side caching operations.
      *
@@ -3953,6 +3981,8 @@
         sGetSlotIndexCache.disableLocal();
         sGetSubIdCache.disableLocal();
         sGetPhoneIdCache.disableLocal();
+
+        sIsSubscriptionManagerServiceEnabled.disableLocal();
     }
 
     /**
@@ -3975,6 +4005,8 @@
         sGetSlotIndexCache.clear();
         sGetSubIdCache.clear();
         sGetPhoneIdCache.clear();
+
+        sIsSubscriptionManagerServiceEnabled.clear();
     }
 
     /**
@@ -4387,5 +4419,70 @@
         }
         return null;
     }
+
+    /**
+     * Check if subscription and user are associated with each other.
+     *
+     * @param subscriptionId the subId of the subscription
+     * @param userHandle user handle of the user
+     * @return {@code true} if subscription is associated with user
+     * {code true} if there are no subscriptions on device
+     * else {@code false} if subscription is not associated with user.
+     *
+     * @throws IllegalArgumentException if subscription is invalid.
+     * @throws SecurityException if the caller doesn't have permissions required.
+     * @throws IllegalStateException if subscription service is not available.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
+    public boolean isSubscriptionAssociatedWithUser(int subscriptionId,
+            @NonNull UserHandle userHandle) {
+        if (!isValidSubscriptionId(subscriptionId)) {
+            throw new IllegalArgumentException("[isSubscriptionAssociatedWithUser]: "
+                    + "Invalid subscriptionId: " + subscriptionId);
+        }
+
+        try {
+            ISub iSub = TelephonyManager.getSubscriptionService();
+            if (iSub != null) {
+                return iSub.isSubscriptionAssociatedWithUser(subscriptionId, userHandle);
+            } else {
+                throw new IllegalStateException("[isSubscriptionAssociatedWithUser]: "
+                        + "subscription service unavailable");
+            }
+        } catch (RemoteException ex) {
+            ex.rethrowAsRuntimeException();
+        }
+        return false;
+    }
+
+    /**
+     * Get list of subscriptions associated with user.
+     *
+     * @param userHandle user handle of the user
+     * @return list of subscriptionInfo associated with the user.
+     *
+     * @throws SecurityException if the caller doesn't have permissions required.
+     * @throws IllegalStateException if subscription service is not available.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
+    public @NonNull List<SubscriptionInfo> getSubscriptionInfoListAssociatedWithUser(
+            @NonNull UserHandle userHandle) {
+        try {
+            ISub iSub = TelephonyManager.getSubscriptionService();
+            if (iSub != null) {
+                return iSub.getSubscriptionInfoListAssociatedWithUser(userHandle);
+            } else {
+                throw new IllegalStateException("[getSubscriptionInfoListAssociatedWithUser]: "
+                        + "subscription service unavailable");
+            }
+        } catch (RemoteException ex) {
+            ex.rethrowAsRuntimeException();
+        }
+        return new ArrayList<>();
+    }
 }
 
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index f4e2ade..e01b10e 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -87,10 +87,12 @@
     public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 5;
 
     @NetworkType
-    private final  int mNetworkType;
+    private final int mNetworkType;
 
     @OverrideNetworkType
-    private final  int mOverrideNetworkType;
+    private final int mOverrideNetworkType;
+
+    private final boolean mIsRoaming;
 
     /**
      * Constructor
@@ -98,18 +100,37 @@
      * @param networkType Current packet-switching cellular network type
      * @param overrideNetworkType The override network type
      *
+     * @deprecated will not use this constructor anymore.
+     * @hide
+     */
+    @Deprecated
+    public TelephonyDisplayInfo(@NetworkType int networkType,
+            @OverrideNetworkType int overrideNetworkType) {
+        this(networkType, overrideNetworkType, false);
+    }
+
+    /**
+     * Constructor
+     *
+     * @param networkType Current packet-switching cellular network type
+     * @param overrideNetworkType The override network type
+     * @param isRoaming True if the device is roaming after override.
+     *
      * @hide
      */
     public TelephonyDisplayInfo(@NetworkType int networkType,
-            @OverrideNetworkType int overrideNetworkType) {
+            @OverrideNetworkType int overrideNetworkType,
+            boolean isRoaming) {
         mNetworkType = networkType;
         mOverrideNetworkType = overrideNetworkType;
+        mIsRoaming = isRoaming;
     }
 
     /** @hide */
     public TelephonyDisplayInfo(Parcel p) {
         mNetworkType = p.readInt();
         mOverrideNetworkType = p.readInt();
+        mIsRoaming = p.readBoolean();
     }
 
     /**
@@ -135,10 +156,25 @@
         return mOverrideNetworkType;
     }
 
+    /**
+     * Get device is roaming or not. Note the isRoaming is for market branding or visualization
+     * purposes only. It cannot be treated as the actual roaming device is camped on.
+     *
+     * @return True if the device is registered on roaming network overridden by config.
+     * @see CarrierConfigManager#KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+     * @see CarrierConfigManager#KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+     * @see CarrierConfigManager#KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY
+     * @see CarrierConfigManager#KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY
+     */
+    public boolean isRoaming() {
+        return mIsRoaming;
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mNetworkType);
         dest.writeInt(mOverrideNetworkType);
+        dest.writeBoolean(mIsRoaming);
     }
 
     public static final @NonNull Parcelable.Creator<TelephonyDisplayInfo> CREATOR =
@@ -165,12 +201,13 @@
         if (o == null || getClass() != o.getClass()) return false;
         TelephonyDisplayInfo that = (TelephonyDisplayInfo) o;
         return mNetworkType == that.mNetworkType
-                && mOverrideNetworkType == that.mOverrideNetworkType;
+                && mOverrideNetworkType == that.mOverrideNetworkType
+                && mIsRoaming == that.mIsRoaming;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mNetworkType, mOverrideNetworkType);
+        return Objects.hash(mNetworkType, mOverrideNetworkType, mIsRoaming);
     }
 
     /**
@@ -195,6 +232,7 @@
     @Override
     public String toString() {
         return "TelephonyDisplayInfo {network=" + TelephonyManager.getNetworkTypeName(mNetworkType)
-                + ", override=" + overrideNetworkTypeToString(mOverrideNetworkType) + "}";
+                + ", overrideNetwork=" + overrideNetworkTypeToString(mOverrideNetworkType)
+                + ", isRoaming=" + mIsRoaming + "}";
     }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d23b75b..fa60031 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -311,6 +311,31 @@
     public static final int SRVCC_STATE_HANDOVER_CANCELED  = 3;
 
     /**
+     * Convert srvcc handover state to string.
+     *
+     * @param state The srvcc handover state.
+     * @return The srvcc handover state in string format.
+     *
+     * @hide
+     */
+    public static @NonNull String srvccStateToString(int state) {
+        switch (state) {
+            case TelephonyManager.SRVCC_STATE_HANDOVER_NONE:
+                return "NONE";
+            case TelephonyManager.SRVCC_STATE_HANDOVER_STARTED:
+                return "STARTED";
+            case TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED:
+                return "COMPLETED";
+            case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED:
+                return "FAILED";
+            case TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED:
+                return "CANCELED";
+            default:
+                return "UNKNOWN(" + state + ")";
+        }
+    }
+
+    /**
      * A UICC card identifier used if the device does not support the operation.
      * For example, {@link #getCardIdForDefaultEuicc()} returns this value if the device has no
      * eUICC, or the eUICC cannot be read.
@@ -17027,4 +17052,44 @@
         }
         return TelephonyManager.SIM_STATE_UNKNOWN;
     }
+
+    /**
+     * Convert SIM state into string.
+     *
+     * @param state SIM state.
+     * @return SIM state in string format.
+     *
+     * @hide
+     */
+    @NonNull
+    public static String simStateToString(@SimState int state) {
+        switch (state) {
+            case TelephonyManager.SIM_STATE_UNKNOWN:
+                return "UNKNOWN";
+            case TelephonyManager.SIM_STATE_ABSENT:
+                return "ABSENT";
+            case TelephonyManager.SIM_STATE_PIN_REQUIRED:
+                return "PIN_REQUIRED";
+            case TelephonyManager.SIM_STATE_PUK_REQUIRED:
+                return "PUK_REQUIRED";
+            case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
+                return "NETWORK_LOCKED";
+            case TelephonyManager.SIM_STATE_READY:
+                return "READY";
+            case TelephonyManager.SIM_STATE_NOT_READY:
+                return "NOT_READY";
+            case TelephonyManager.SIM_STATE_PERM_DISABLED:
+                return "PERM_DISABLED";
+            case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
+                return "CARD_IO_ERROR";
+            case TelephonyManager.SIM_STATE_CARD_RESTRICTED:
+                return "CARD_RESTRICTED";
+            case TelephonyManager.SIM_STATE_LOADED:
+                return "LOADED";
+            case TelephonyManager.SIM_STATE_PRESENT:
+                return "PRESENT";
+            default:
+                return "UNKNOWN(" + state + ")";
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index 19f2a9b..b761709 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -153,8 +153,9 @@
                     nsi = mScanInfo.get(message.arg2);
                 }
                 if (nsi == null) {
-                    throw new RuntimeException(
-                        "Failed to find NetworkScanInfo with id " + message.arg2);
+                    Rlog.e(TAG, "Unexpceted message " + message.what
+                            + " as there is no NetworkScanInfo with id " + message.arg2);
+                    return;
                 }
 
                 final NetworkScanCallback callback = nsi.mCallback;
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index c5f6902..defa046 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -326,4 +326,41 @@
      * @throws IllegalArgumentException if subId is invalid.
      */
      UserHandle getSubscriptionUserHandle(int subId);
+
+     /**
+      * Check if subscription and user are associated with each other.
+      *
+      * @param subscriptionId the subId of the subscription
+      * @param userHandle user handle of the user
+      * @return {@code true} if subscription is associated with user
+      * {code true} if there are no subscriptions on device
+      * else {@code false} if subscription is not associated with user.
+      *
+      * @throws IllegalArgumentException if subscription is invalid.
+      * @throws SecurityException if the caller doesn't have permissions required.
+      * @throws IllegalStateException if subscription service is not available.
+      *
+      * @hide
+      */
+      boolean isSubscriptionAssociatedWithUser(int subscriptionId, in UserHandle userHandle);
+
+      /**
+       * Get list of subscriptions associated with user.
+       *
+       * @param userHandle user handle of the user
+       * @return list of subscriptionInfo associated with the user.
+       *
+       * @throws SecurityException if the caller doesn't have permissions required.
+       * @throws IllegalStateException if subscription service is not available.
+       *
+       * @hide
+       */
+       List<SubscriptionInfo> getSubscriptionInfoListAssociatedWithUser(in UserHandle userHandle);
+
+       /**
+        * @return {@code true} if using SubscriptionManagerService instead of
+        * SubscriptionController.
+        */
+       //TODO: Removed before U AOSP public release.
+       boolean isSubscriptionManagerServiceEnabled();
 }
diff --git a/tests/ActivityManagerPerfTests/utils/Android.bp b/tests/ActivityManagerPerfTests/utils/Android.bp
index 99c43c8..5902c1c 100644
--- a/tests/ActivityManagerPerfTests/utils/Android.bp
+++ b/tests/ActivityManagerPerfTests/utils/Android.bp
@@ -32,6 +32,6 @@
     static_libs: [
         "androidx.test.rules",
         "junit",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
     ],
 }
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java
index fc787ba..9bd94f2 100644
--- a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java
@@ -19,10 +19,10 @@
 import android.content.Intent;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
-import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
 
 import java.io.IOException;
 
diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
index 5430dee..afa4ff8 100644
--- a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
+++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
@@ -84,7 +84,7 @@
     // avoid flakiness we run these tests multiple times, allowing progressively longer between
     // code loading and checking the logs on each try.)
     private static final int AUDIT_LOG_RETRIES = 10;
-    private static final int RETRY_DELAY_MS = 2_000;
+    private static final int RETRY_DELAY_MS = 500;
 
     private static Context sContext;
     private static int sMyUid;
@@ -245,7 +245,7 @@
                 "/DynamicCodeLoggerNativeExecutable", privateCopyFile);
 
         EventLog.writeEvent(EventLog.getTagCode("auditd"),
-                "type=1400 avc: granted { execute_no_trans } "
+                "type=1400 avc:  granted  { execute_no_trans } "
                         + "path=\"" + privateCopyFile + "\" "
                         + "scontext=u:r:untrusted_app: "
                         + "tcontext=u:object_r:app_data_file: "
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 7731e09..5301d67 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -47,6 +47,9 @@
         "launcher-aosp-tapl",
         "platform-test-annotations",
     ],
+    data: [
+        ":FlickerTestApp",
+    ],
 }
 
 java_library {
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index de9bbb6..a92a6ae 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -21,7 +21,7 @@
         "services.core.unboosted",
         "testables",
         "truth-prebuilt",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
     ],
     test_suites: ["device-tests"],
 }
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 1d65cc3..8025406 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -27,14 +27,15 @@
 import android.os.SystemClock
 import android.provider.Settings
 import android.provider.Settings.Global.HIDE_ERROR_DIALOGS
-import android.support.test.uiautomator.By
-import android.support.test.uiautomator.UiDevice
-import android.support.test.uiautomator.UiObject2
-import android.support.test.uiautomator.Until
 import android.testing.PollingCheck
 import android.view.InputDevice
 import android.view.MotionEvent
 
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
diff --git a/tests/OdmApps/Android.bp b/tests/OdmApps/Android.bp
index de86498..a5c6d65 100644
--- a/tests/OdmApps/Android.bp
+++ b/tests/OdmApps/Android.bp
@@ -26,4 +26,8 @@
     srcs: ["src/**/*.java"],
     libs: ["tradefed"],
     test_suites: ["device-tests"],
+    data: [
+        ":TestOdmApp",
+        ":TestOdmPrivApp",
+    ],
 }
diff --git a/tests/SharedLibraryLoadingTest/AndroidTest.xml b/tests/SharedLibraryLoadingTest/AndroidTest.xml
index 947453d..ad05847 100644
--- a/tests/SharedLibraryLoadingTest/AndroidTest.xml
+++ b/tests/SharedLibraryLoadingTest/AndroidTest.xml
@@ -22,7 +22,6 @@
 
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-        <option name="cleanup" value="false" />
         <option name="remount-system" value="true" />
         <option name="push"
                 value="SharedLibraryLoadingTests_StandardSharedLibrary.apk->/product/app/SharedLibraryLoadingTests_StandardSharedLibrary.apk" />
diff --git a/tests/SoundTriggerTestApp/OWNERS b/tests/SoundTriggerTestApp/OWNERS
index 9db19a3..a0fcfc5 100644
--- a/tests/SoundTriggerTestApp/OWNERS
+++ b/tests/SoundTriggerTestApp/OWNERS
@@ -1,2 +1,2 @@
-include /core/java/android/media/soundtrigger/OWNERS
+include /media/java/android/media/soundtrigger/OWNERS
 mdooley@google.com
diff --git a/tests/SoundTriggerTests/OWNERS b/tests/SoundTriggerTests/OWNERS
index 816bc6b..1e41886 100644
--- a/tests/SoundTriggerTests/OWNERS
+++ b/tests/SoundTriggerTests/OWNERS
@@ -1 +1 @@
-include /core/java/android/media/soundtrigger/OWNERS
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index cce0dde..38e7447 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -55,6 +55,7 @@
         "cts-install-lib-host",
     ],
     data: [
+        ":StagedInstallInternalTestApp",
         ":apex.apexd_test",
         ":com.android.apex.apkrollback.test_v1",
         ":StagedInstallTestApexV2",
diff --git a/tests/SystemMemoryTest/host/Android.bp b/tests/SystemMemoryTest/host/Android.bp
index 7974462..cc8bc45 100644
--- a/tests/SystemMemoryTest/host/Android.bp
+++ b/tests/SystemMemoryTest/host/Android.bp
@@ -26,4 +26,7 @@
     srcs: ["src/**/*.java"],
     libs: ["tradefed"],
     test_suites: ["general-tests"],
+    data: [
+        ":SystemMemoryTestDevice",
+    ],
 }
diff --git a/tests/WindowAnimationJank/Android.bp b/tests/WindowAnimationJank/Android.bp
index ed86aa5..8542f88 100644
--- a/tests/WindowAnimationJank/Android.bp
+++ b/tests/WindowAnimationJank/Android.bp
@@ -25,7 +25,7 @@
     name: "WindowAnimationJank",
     srcs: ["src/**/*.java"],
     static_libs: [
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "androidx.test.janktesthelper",
         "junit",
     ],
diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java b/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java
index 2531464..48a359c 100644
--- a/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java
+++ b/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java
@@ -18,11 +18,12 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.SystemClock;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
+
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
 
 /**
  * Set of helpers to manipulate test activities.
diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java b/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java
index a8ace162..cb7c511 100644
--- a/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java
+++ b/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java
@@ -16,9 +16,8 @@
 
 package android.windowanimationjank;
 
-import android.support.test.uiautomator.UiDevice;
-
 import androidx.test.jank.JankTestBase;
+import androidx.test.uiautomator.UiDevice;
 
 /**
  * This adds additional system level jank monitor and its result is merged with primary monitor
diff --git a/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java b/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java
new file mode 100644
index 0000000..b94bb41
--- /dev/null
+++ b/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 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.os.test;
+
+import static android.permission.PermissionManager.PERMISSION_GRANTED;
+import static android.permission.PermissionManager.PERMISSION_HARD_DENIED;
+
+import android.annotation.NonNull;
+import android.content.AttributionSource;
+import android.os.PermissionEnforcer;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Fake for {@link PermissionEnforcer}. Useful for tests wanting to mock the
+ * permission checks of an AIDL service. FakePermissionEnforcer may be passed
+ * to the constructor of the AIDL-generated Stub class.
+ *
+ */
+public class FakePermissionEnforcer extends PermissionEnforcer {
+    private Set<String> mGranted;
+
+    public FakePermissionEnforcer() {
+        mGranted = new HashSet();
+    }
+
+    public void grant(String permission) {
+        mGranted.add(permission);
+    }
+
+    public void revoke(String permission) {
+        mGranted.remove(permission);
+    }
+
+    private boolean granted(String permission) {
+        return mGranted.contains(permission);
+    }
+
+    @Override
+    protected int checkPermission(@NonNull String permission,
+              @NonNull AttributionSource source) {
+        return granted(permission) ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+    }
+
+    @Override
+    protected int checkPermission(@NonNull String permission, int pid, int uid) {
+        return granted(permission) ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+    }
+}
diff --git a/tests/utils/testutils/java/android/os/test/OWNERS b/tests/utils/testutils/java/android/os/test/OWNERS
new file mode 100644
index 0000000..3a9129e
--- /dev/null
+++ b/tests/utils/testutils/java/android/os/test/OWNERS
@@ -0,0 +1 @@
+per-file FakePermissionEnforcer.java = file:/tests/EnforcePermission/OWNERS
diff --git a/tests/vcn/java/android/net/vcn/VcnConfigTest.java b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
index b313c9f..73a0a61 100644
--- a/tests/vcn/java/android/net/vcn/VcnConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
@@ -17,6 +17,7 @@
 package android.net.vcn;
 
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import static org.junit.Assert.assertEquals;
@@ -160,6 +161,37 @@
         assertNotEquals(config, configNotEqual);
     }
 
+    private VcnConfig buildConfigRestrictTransportTest(boolean isTestMode) throws Exception {
+        VcnConfig.Builder builder =
+                new VcnConfig.Builder(mContext)
+                        .setRestrictedUnderlyingNetworkTransports(Set.of(TRANSPORT_TEST));
+        if (isTestMode) {
+            builder.setIsTestModeProfile();
+        }
+
+        for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) {
+            builder.addGatewayConnectionConfig(gatewayConnectionConfig);
+        }
+
+        return builder.build();
+    }
+
+    @Test
+    public void testRestrictTransportTestInTestModeProfile() throws Exception {
+        final VcnConfig config = buildConfigRestrictTransportTest(true /*  isTestMode */);
+        assertEquals(Set.of(TRANSPORT_TEST), config.getRestrictedUnderlyingNetworkTransports());
+    }
+
+    @Test
+    public void testRestrictTransportTestInNonTestModeProfile() throws Exception {
+        try {
+            buildConfigRestrictTransportTest(false /*  isTestMode */);
+            fail("Expected exception because the config is not a test mode profile");
+        } catch (Exception expected) {
+
+        }
+    }
+
     @Test
     public void testParceling() {
         final VcnConfig config = buildTestConfig(mContext);
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 075bc5e..4123f80 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -17,7 +17,9 @@
 package com.android.server;
 
 import static android.net.ConnectivityManager.NetworkCallback;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -67,7 +69,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.net.TelephonyNetworkSpecifier;
 import android.net.Uri;
 import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
@@ -128,6 +129,15 @@
     private static final VcnConfig TEST_VCN_CONFIG;
     private static final VcnConfig TEST_VCN_CONFIG_PKG_2;
     private static final int TEST_UID = Process.FIRST_APPLICATION_UID;
+    private static final String TEST_IFACE_NAME = "TEST_IFACE";
+    private static final String TEST_IFACE_NAME_2 = "TEST_IFACE2";
+    private static final LinkProperties TEST_LP_1 = new LinkProperties();
+    private static final LinkProperties TEST_LP_2 = new LinkProperties();
+
+    static {
+        TEST_LP_1.setInterfaceName(TEST_IFACE_NAME);
+        TEST_LP_2.setInterfaceName(TEST_IFACE_NAME_2);
+    }
 
     static {
         final Context mockConfigContext = mock(Context.class);
@@ -1034,8 +1044,7 @@
         setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive);
 
         return mVcnMgmtSvc.getUnderlyingNetworkPolicy(
-                getNetworkCapabilitiesBuilderForTransport(subId, transport).build(),
-                new LinkProperties());
+                getNetworkCapabilitiesBuilderForTransport(subId, transport).build(), TEST_LP_1);
     }
 
     private void checkGetRestrictedTransportsFromCarrierConfig(
@@ -1260,7 +1269,7 @@
                 false /* expectRestricted */);
     }
 
-    private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) {
+    private void setupTrackedNetwork(NetworkCapabilities caps, LinkProperties lp) {
         mVcnMgmtSvc.systemReady();
 
         final ArgumentCaptor<NetworkCallback> captor =
@@ -1269,7 +1278,10 @@
                 .registerNetworkCallback(
                         eq(new NetworkRequest.Builder().clearCapabilities().build()),
                         captor.capture());
-        captor.getValue().onCapabilitiesChanged(mock(Network.class, CALLS_REAL_METHODS), caps);
+
+        Network mockNetwork = mock(Network.class, CALLS_REAL_METHODS);
+        captor.getValue().onCapabilitiesChanged(mockNetwork, caps);
+        captor.getValue().onLinkPropertiesChanged(mockNetwork, lp);
     }
 
     @Test
@@ -1279,7 +1291,7 @@
                 getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
                         .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
                         .build();
-        setupTrackedCarrierWifiNetwork(existingNetworkCaps);
+        setupTrackedNetwork(existingNetworkCaps, TEST_LP_1);
 
         // Trigger test without VCN instance alive; expect restart due to change of NOT_RESTRICTED
         // immutable capability
@@ -1288,7 +1300,7 @@
                         getNetworkCapabilitiesBuilderForTransport(
                                         TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
                                 .build(),
-                        new LinkProperties());
+                        TEST_LP_1);
         assertTrue(policy.isTeardownRequested());
     }
 
@@ -1298,7 +1310,7 @@
         final NetworkCapabilities existingNetworkCaps =
                 getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
                         .build();
-        setupTrackedCarrierWifiNetwork(existingNetworkCaps);
+        setupTrackedNetwork(existingNetworkCaps, TEST_LP_1);
 
         final VcnUnderlyingNetworkPolicy policy =
                 startVcnAndGetPolicyForTransport(
@@ -1315,7 +1327,7 @@
                         .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
                         .removeCapability(NET_CAPABILITY_IMS)
                         .build();
-        setupTrackedCarrierWifiNetwork(existingNetworkCaps);
+        setupTrackedNetwork(existingNetworkCaps, TEST_LP_1);
 
         final VcnUnderlyingNetworkPolicy policy =
                 mVcnMgmtSvc.getUnderlyingNetworkPolicy(
@@ -1336,7 +1348,7 @@
                 new NetworkCapabilities.Builder()
                         .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                         .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
-                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_2))
+                        .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID_2))
                         .build();
 
         VcnUnderlyingNetworkPolicy policy =
@@ -1346,6 +1358,38 @@
         assertEquals(nc, policy.getMergedNetworkCapabilities());
     }
 
+    /**
+     * Checks that networks with similar capabilities do not clobber each other.
+     *
+     * <p>In previous iterations, the VcnMgmtSvc used capability-matching to check if a network
+     * undergoing policy checks were the same as an existing networks. However, this meant that if
+     * there were newly added capabilities that the VCN did not check, two networks differing only
+     * by that capability would restart each other constantly.
+     */
+    @Test
+    public void testGetUnderlyingNetworkPolicySimilarNetworks() throws Exception {
+        NetworkCapabilities nc1 =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+                        .addCapability(NET_CAPABILITY_INTERNET)
+                        .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID_2))
+                        .build();
+
+        NetworkCapabilities nc2 =
+                new NetworkCapabilities.Builder(nc1)
+                        .addCapability(NET_CAPABILITY_ENTERPRISE)
+                        .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                        .build();
+
+        setupTrackedNetwork(nc1, TEST_LP_1);
+
+        VcnUnderlyingNetworkPolicy policy = mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc2, TEST_LP_2);
+
+        assertFalse(policy.isTeardownRequested());
+        assertEquals(nc2, policy.getMergedNetworkCapabilities());
+    }
+
     @Test(expected = SecurityException.class)
     public void testGetUnderlyingNetworkPolicyInvalidPermission() {
         doReturn(PackageManager.PERMISSION_DENIED)
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index 629e988..2266041 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -95,6 +95,7 @@
     private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES =
             new NetworkCapabilities.Builder()
                     .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                     .setSignalStrength(WIFI_RSSI)
                     .setSsid(SSID)
                     .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
@@ -509,12 +510,14 @@
             VcnCellUnderlyingNetworkTemplate template, boolean expectMatch) {
         assertEquals(
                 expectMatch,
-                checkMatchesCellPriorityRule(
+                checkMatchesPriorityRule(
                         mVcnContext,
                         template,
                         mCellNetworkRecord,
                         SUB_GROUP,
-                        mSubscriptionSnapshot));
+                        mSubscriptionSnapshot,
+                        null /* currentlySelected */,
+                        null /* carrierConfig */));
     }
 
     @Test
diff --git a/tools/lint/README.md b/tools/lint/README.md
index b534b62..b235ad6 100644
--- a/tools/lint/README.md
+++ b/tools/lint/README.md
@@ -1,15 +1,44 @@
-# Android Framework Lint Checker
+# Android Lint Checks for AOSP
 
-Custom lint checks written here are going to be executed for modules that opt in to those (e.g. any
+Custom Android Lint checks are written here to be executed against java modules
+in AOSP. These checks are broken down into two subdirectories:
+
+1. [Global Checks](#android-global-lint-checker)
+2. [Framework Checks](#android-framework-lint-checker)
+
+# [Android Global Lint Checker](/global)
+Checks written here are executed for the entire tree. The `AndroidGlobalLintChecker`
+build target produces a jar file that is included in the overall build output
+(`AndroidGlobalLintChecker.jar`).  This file is then downloaded as a prebuilt under the
+`prebuilts/cmdline-tools` subproject, and included by soong with all invocations of lint.
+
+## How to add new global lint checks
+1. Write your detector with its issues and put it into
+   `global/checks/src/main/java/com/google/android/lint`.
+2. Add your detector's issues into `AndroidGlobalIssueRegistry`'s `issues`
+   field.
+3. Write unit tests for your detector in one file and put it into
+   `global/checks/test/java/com/google/android/lint`.
+4. Have your change reviewed and merged.  Once your change is merged,
+   obtain a build number from a successful build that includes your change.
+5. Run `prebuilts/cmdline-tools/update-android-global-lint-checker.sh
+   <build_number>`. The script will create a commit that you can upload for
+   approval to the `prebuilts/cmdline-tools` subproject.
+6. Done! Your lint check should be applied in lint report builds across the
+   entire tree!
+
+# [Android Framework Lint Checker](/framework)
+
+Checks written here are going to be executed for modules that opt in to those (e.g. any
 `services.XXX` module) and results will be automatically reported on CLs on gerrit.
 
-## How to add new lint checks
+## How to add new framework lint checks
 
 1. Write your detector with its issues and put it into
-   `checks/src/main/java/com/google/android/lint`.
+   `framework/checks/src/main/java/com/google/android/lint`.
 2. Add your detector's issues into `AndroidFrameworkIssueRegistry`'s `issues` field.
 3. Write unit tests for your detector in one file and put it into
-   `checks/test/java/com/google/android/lint`.
+   `framework/checks/test/java/com/google/android/lint`.
 4. Done! Your lint checks should be applied in lint report builds for modules that include
    `AndroidFrameworkLintChecker`.
 
@@ -44,7 +73,11 @@
   environment variable with the id of the lint. For example:
   `ANDROID_LINT_CHECK=UnusedTokenOfOriginalCallingIdentity m out/[...]/lint-report.html`
 
-## Create or update a baseline
+# How to apply automatic fixes suggested by lint
+
+See [lint_fix](fix/README.md)
+
+# Create or update a baseline
 
 Baseline files can be used to silence known errors (and warnings) that are deemed to be safe. When
 there is a lint-baseline.xml file in the root folder of the java library, soong will
@@ -75,9 +108,10 @@
 [lint.go](http://cs/aosp-master/build/soong/java/lint.go;l=451;rcl=2e778d5bc4a8d1d77b4f4a3029a4a254ad57db75)
 adding `cmd.Flag("--nowarn")` and running lint again.
 
-## Documentation
+# Documentation
 
 - [Android Lint Docs](https://googlesamples.github.io/android-custom-lint-rules/)
+- [Lint Check Unit Testing](https://googlesamples.github.io/android-custom-lint-rules/api-guide/unit-testing.md.html)
 - [Android Lint source files](https://source.corp.google.com/studio-main/tools/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/)
 - [PSI source files](https://github.com/JetBrains/intellij-community/tree/master/java/java-psi-api/src/com/intellij/psi)
 - [UAST source files](https://upsource.jetbrains.com/idea-ce/structure/idea-ce-7b9b8cc138bbd90aec26433f82cd2c6838694003/uast/uast-common/src/org/jetbrains/uast)
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt
deleted file mode 100644
index 8f553ab..0000000
--- a/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2022 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.google.android.lint
-
-import com.android.tools.lint.detector.api.AnnotationInfo
-import com.android.tools.lint.detector.api.AnnotationOrigin
-import com.android.tools.lint.detector.api.AnnotationUsageInfo
-import com.android.tools.lint.detector.api.AnnotationUsageType
-import com.android.tools.lint.detector.api.ConstantEvaluator
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import com.intellij.psi.PsiAnnotation
-import com.intellij.psi.PsiClass
-import com.intellij.psi.PsiMethod
-import org.jetbrains.uast.UElement
-
-/**
- * Lint Detector that ensures that any method overriding a method annotated
- * with @EnforcePermission is also annotated with the exact same annotation.
- * The intent is to surface the effective permission checks to the service
- * implementations.
- */
-class EnforcePermissionDetector : Detector(), SourceCodeScanner {
-
-    val ENFORCE_PERMISSION = "android.annotation.EnforcePermission"
-
-    override fun applicableAnnotations(): List<String> {
-        return listOf(ENFORCE_PERMISSION)
-    }
-
-    private fun areAnnotationsEquivalent(
-        context: JavaContext,
-        anno1: PsiAnnotation,
-        anno2: PsiAnnotation
-    ): Boolean {
-        if (anno1.qualifiedName != anno2.qualifiedName) {
-            return false
-        }
-        val attr1 = anno1.parameterList.attributes
-        val attr2 = anno2.parameterList.attributes
-        if (attr1.size != attr2.size) {
-            return false
-        }
-        for (i in attr1.indices) {
-            if (attr1[i].name != attr2[i].name) {
-                return false
-            }
-            val value1 = attr1[i].value
-            val value2 = attr2[i].value
-            if (value1 == null && value2 == null) {
-                continue
-            }
-            if (value1 == null || value2 == null) {
-                return false
-            }
-            val v1 = ConstantEvaluator.evaluate(context, value1)
-            val v2 = ConstantEvaluator.evaluate(context, value2)
-            if (v1 != v2) {
-                return false
-            }
-        }
-        return true
-    }
-
-    override fun visitAnnotationUsage(
-        context: JavaContext,
-        element: UElement,
-        annotationInfo: AnnotationInfo,
-        usageInfo: AnnotationUsageInfo
-    ) {
-        if (usageInfo.type == AnnotationUsageType.EXTENDS) {
-            val newClass = element.sourcePsi?.parent?.parent as PsiClass
-            val extendedClass: PsiClass = usageInfo.referenced as PsiClass
-            val newAnnotation = newClass.getAnnotation(ENFORCE_PERMISSION)
-            val extendedAnnotation = extendedClass.getAnnotation(ENFORCE_PERMISSION)!!
-
-            val location = context.getLocation(element)
-            val newClassName = newClass.qualifiedName
-            val extendedClassName = extendedClass.qualifiedName
-            if (newAnnotation == null) {
-                val msg = "The class $newClassName extends the class $extendedClassName which " +
-                    "is annotated with @EnforcePermission. The same annotation must be used " +
-                    "on $newClassName."
-                context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg)
-            } else if (!areAnnotationsEquivalent(context, newAnnotation, extendedAnnotation)) {
-                val msg = "The class $newClassName is annotated with ${newAnnotation.text} " +
-                    "which differs from the parent class $extendedClassName: " +
-                    "${extendedAnnotation.text}. The same annotation must be used for " +
-                    "both classes."
-                context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg)
-            }
-        } else if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE &&
-            annotationInfo.origin == AnnotationOrigin.METHOD) {
-            val overridingMethod = element.sourcePsi as PsiMethod
-            val overriddenMethod = usageInfo.referenced as PsiMethod
-            val overridingAnnotation = overridingMethod.getAnnotation(ENFORCE_PERMISSION)
-            val overriddenAnnotation = overriddenMethod.getAnnotation(ENFORCE_PERMISSION)!!
-
-            val location = context.getLocation(element)
-            val overridingClass = overridingMethod.parent as PsiClass
-            val overriddenClass = overriddenMethod.parent as PsiClass
-            val overridingName = "${overridingClass.name}.${overridingMethod.name}"
-            val overriddenName = "${overriddenClass.name}.${overriddenMethod.name}"
-            if (overridingAnnotation == null) {
-                val msg = "The method $overridingName overrides the method $overriddenName which " +
-                    "is annotated with @EnforcePermission. The same annotation must be used " +
-                    "on $overridingName"
-                context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg)
-            } else if (!areAnnotationsEquivalent(
-                        context, overridingAnnotation, overriddenAnnotation)) {
-                val msg = "The method $overridingName is annotated with " +
-                    "${overridingAnnotation.text} which differs from the overridden " +
-                    "method $overriddenName: ${overriddenAnnotation.text}. The same " +
-                    "annotation must be used for both methods."
-                context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg)
-            }
-        }
-    }
-
-    companion object {
-        val EXPLANATION = """
-            The @EnforcePermission annotation is used to indicate that the underlying binder code
-            has already verified the caller's permissions before calling the appropriate method. The
-            verification code is usually generated by the AIDL compiler, which also takes care of
-            annotating the generated Java code.
-
-            In order to surface that information to platform developers, the same annotation must be
-            used on the implementation class or methods.
-            """
-
-        val ISSUE_MISSING_ENFORCE_PERMISSION: Issue = Issue.create(
-            id = "MissingEnforcePermissionAnnotation",
-            briefDescription = "Missing @EnforcePermission annotation on Binder method",
-            explanation = EXPLANATION,
-            category = Category.SECURITY,
-            priority = 6,
-            severity = Severity.ERROR,
-            implementation = Implementation(
-                    EnforcePermissionDetector::class.java,
-                    Scope.JAVA_FILE_SCOPE
-            )
-        )
-
-        val ISSUE_MISMATCHING_ENFORCE_PERMISSION: Issue = Issue.create(
-            id = "MismatchingEnforcePermissionAnnotation",
-            briefDescription = "Incorrect @EnforcePermission annotation on Binder method",
-            explanation = EXPLANATION,
-            category = Category.SECURITY,
-            priority = 6,
-            severity = Severity.ERROR,
-            implementation = Implementation(
-                    EnforcePermissionDetector::class.java,
-                    Scope.JAVA_FILE_SCOPE
-            )
-        )
-    }
-}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt
deleted file mode 100644
index f5f4ebe..0000000
--- a/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2022 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.google.android.lint
-
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
-import com.android.tools.lint.checks.infrastructure.TestLintTask
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Issue
-
-@Suppress("UnstableApiUsage")
-class EnforcePermissionDetectorTest : LintDetectorTest() {
-    override fun getDetector(): Detector = EnforcePermissionDetector()
-
-    override fun getIssues(): List<Issue> = listOf(
-            EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
-            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION
-    )
-
-    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
-
-    fun testDoesNotDetectIssuesCorrectAnnotationOnClass() {
-        lint().files(java(
-            """
-            package test.pkg;
-            @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-            public class TestClass1 extends IFoo.Stub {
-                public void testMethod() {}
-            }
-            """).indented(),
-                *stubs
-        )
-        .run()
-        .expectClean()
-    }
-
-    fun testDoesNotDetectIssuesCorrectAnnotationOnMethod() {
-        lint().files(java(
-            """
-            package test.pkg;
-            import android.annotation.EnforcePermission;
-            public class TestClass2 extends IFooMethod.Stub {
-                @Override
-                @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-                public void testMethod() {}
-            }
-            """).indented(),
-                *stubs
-        )
-        .run()
-        .expectClean()
-    }
-
-    fun testDetectIssuesMismatchingAnnotationOnClass() {
-        lint().files(java(
-            """
-            package test.pkg;
-            @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
-            public class TestClass3 extends IFoo.Stub {
-                public void testMethod() {}
-            }
-            """).indented(),
-                *stubs
-        )
-        .run()
-        .expect("""src/test/pkg/TestClass3.java:3: Error: The class test.pkg.TestClass3 is \
-annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
-which differs from the parent class IFoo.Stub: \
-@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The \
-same annotation must be used for both classes. [MismatchingEnforcePermissionAnnotation]
-public class TestClass3 extends IFoo.Stub {
-                                ~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
-    }
-
-    fun testDetectIssuesMismatchingAnnotationOnMethod() {
-        lint().files(java(
-            """
-            package test.pkg;
-            public class TestClass4 extends IFooMethod.Stub {
-                @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
-                public void testMethod() {}
-            }
-            """).indented(),
-                *stubs
-        )
-        .run()
-        .expect("""src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is \
-annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
-which differs from the overridden method Stub.testMethod: \
-@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same \
-annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
-    public void testMethod() {}
-                ~~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
-    }
-
-    fun testDetectIssuesMissingAnnotationOnClass() {
-        lint().files(java(
-            """
-            package test.pkg;
-            public class TestClass5 extends IFoo.Stub {
-                public void testMethod() {}
-            }
-            """).indented(),
-                *stubs
-        )
-        .run()
-        .expect("""src/test/pkg/TestClass5.java:2: Error: The class test.pkg.TestClass5 extends \
-the class IFoo.Stub which is annotated with @EnforcePermission. The same annotation must be \
-used on test.pkg.TestClass5. [MissingEnforcePermissionAnnotation]
-public class TestClass5 extends IFoo.Stub {
-                                ~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
-    }
-
-    fun testDetectIssuesMissingAnnotationOnMethod() {
-        lint().files(java(
-            """
-            package test.pkg;
-            public class TestClass6 extends IFooMethod.Stub {
-                public void testMethod() {}
-            }
-            """).indented(),
-                *stubs
-        )
-        .run()
-        .expect("""src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod \
-overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same \
-annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation]
-    public void testMethod() {}
-                ~~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
-    }
-
-    /* Stubs */
-
-    private val interfaceIFooStub: TestFile = java(
-        """
-        @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-        public interface IFoo {
-         @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-         public static abstract class Stub implements IFoo {
-           @Override
-           public void testMethod() {}
-         }
-         public void testMethod();
-        }
-        """
-    ).indented()
-
-    private val interfaceIFooMethodStub: TestFile = java(
-        """
-        public interface IFooMethod {
-         public static abstract class Stub implements IFooMethod {
-            @Override
-            @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-            public void testMethod() {}
-          }
-          @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-          public void testMethod();
-        }
-        """
-    ).indented()
-
-    private val manifestPermissionStub: TestFile = java(
-        """
-        package android.Manifest;
-        class permission {
-          public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
-          public static final String INTERNET = "android.permission.INTERNET";
-        }
-        """
-    ).indented()
-
-    private val enforcePermissionAnnotationStub: TestFile = java(
-        """
-        package android.annotation;
-        public @interface EnforcePermission {}
-        """
-    ).indented()
-
-    private val stubs = arrayOf(interfaceIFooStub, interfaceIFooMethodStub,
-            manifestPermissionStub, enforcePermissionAnnotationStub)
-
-    // Substitutes "backslash + new line" with an empty string to imitate line continuation
-    private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")
-}
diff --git a/tools/lint/common/Android.bp b/tools/lint/common/Android.bp
new file mode 100644
index 0000000..898f88b
--- /dev/null
+++ b/tools/lint/common/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2022 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library_host {
+    name: "AndroidCommonLint",
+    srcs: ["src/main/java/**/*.kt"],
+    libs: ["lint_api"],
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/tools/lint/common/src/main/java/com/google/android/lint/Constants.kt b/tools/lint/common/src/main/java/com/google/android/lint/Constants.kt
new file mode 100644
index 0000000..0ef165f
--- /dev/null
+++ b/tools/lint/common/src/main/java/com/google/android/lint/Constants.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint
+
+import com.google.android.lint.model.Method
+
+const val CLASS_STUB = "Stub"
+const val CLASS_CONTEXT = "android.content.Context"
+const val CLASS_ACTIVITY_MANAGER_SERVICE = "com.android.server.am.ActivityManagerService"
+const val CLASS_ACTIVITY_MANAGER_INTERNAL = "android.app.ActivityManagerInternal"
+
+// Enforce permission APIs
+val ENFORCE_PERMISSION_METHODS = listOf(
+        Method(CLASS_CONTEXT, "checkPermission"),
+        Method(CLASS_CONTEXT, "checkCallingPermission"),
+        Method(CLASS_CONTEXT, "checkCallingOrSelfPermission"),
+        Method(CLASS_CONTEXT, "enforcePermission"),
+        Method(CLASS_CONTEXT, "enforceCallingPermission"),
+        Method(CLASS_CONTEXT, "enforceCallingOrSelfPermission"),
+        Method(CLASS_ACTIVITY_MANAGER_SERVICE, "checkPermission"),
+        Method(CLASS_ACTIVITY_MANAGER_INTERNAL, "enforceCallingPermission")
+)
+
+const val ANNOTATION_PERMISSION_METHOD = "android.annotation.PermissionMethod"
+const val ANNOTATION_PERMISSION_NAME = "android.annotation.PermissionName"
+const val ANNOTATION_PERMISSION_RESULT = "android.content.pm.PackageManager.PermissionResult"
diff --git a/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt
new file mode 100644
index 0000000..9a7f8fa
--- /dev/null
+++ b/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint
+
+import com.android.tools.lint.detector.api.getUMethod
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UParameter
+import org.jetbrains.uast.UQualifiedReferenceExpression
+
+fun isPermissionMethodCall(callExpression: UCallExpression): Boolean {
+    val method = callExpression.resolve()?.getUMethod() ?: return false
+    return hasPermissionMethodAnnotation(method)
+}
+
+fun hasPermissionMethodAnnotation(method: UMethod): Boolean =
+        getPermissionMethodAnnotation(method) != null
+
+fun getPermissionMethodAnnotation(method: UMethod?): UAnnotation? = method?.uAnnotations
+        ?.firstOrNull { it.qualifiedName == ANNOTATION_PERMISSION_METHOD }
+
+fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any {
+    it.hasQualifiedName(ANNOTATION_PERMISSION_NAME)
+}
+
+/**
+ * Attempts to return a CallExpression from a QualifiedReferenceExpression (or returns it directly if passed directly)
+ * @param callOrReferenceCall expected to be UCallExpression or UQualifiedReferenceExpression
+ * @return UCallExpression, if available
+ */
+fun findCallExpression(callOrReferenceCall: UElement?): UCallExpression? =
+        when (callOrReferenceCall) {
+            is UCallExpression -> callOrReferenceCall
+            is UQualifiedReferenceExpression -> callOrReferenceCall.selector as? UCallExpression
+            else -> null
+        }
diff --git a/core/java/android/nfc/BeamShareData.aidl b/tools/lint/common/src/main/java/com/google/android/lint/model/Method.kt
similarity index 67%
rename from core/java/android/nfc/BeamShareData.aidl
rename to tools/lint/common/src/main/java/com/google/android/lint/model/Method.kt
index a47e240..3939b61 100644
--- a/core/java/android/nfc/BeamShareData.aidl
+++ b/tools/lint/common/src/main/java/com/google/android/lint/model/Method.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,6 +14,13 @@
  * limitations under the License.
  */
 
-package android.nfc;
+package com.google.android.lint.model
 
-parcelable BeamShareData;
+/**
+ * Data class to represent a Method
+ */
+data class Method(val clazz: String, val name: String) {
+    override fun toString(): String {
+        return "$clazz#$name"
+    }
+}
diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp
new file mode 100644
index 0000000..43f2122
--- /dev/null
+++ b/tools/lint/fix/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2022 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+python_binary_host {
+    name: "lint_fix",
+    main: "soong_lint_fix.py",
+    srcs: ["soong_lint_fix.py"],
+}
+
+python_library_host {
+    name: "soong_lint_fix",
+    srcs: ["soong_lint_fix.py"],
+}
diff --git a/tools/lint/fix/README.md b/tools/lint/fix/README.md
new file mode 100644
index 0000000..a5ac2be
--- /dev/null
+++ b/tools/lint/fix/README.md
@@ -0,0 +1,30 @@
+# Refactoring the platform with lint
+Inspiration: go/refactor-the-platform-with-lint\
+**Special Thanks: brufino@, azharaa@, for the prior work that made this all possible**
+
+## What is this?
+
+It's a python script that runs the framework linter,
+and then (optionally) copies modified files back into the source tree.\
+Why python, you ask?  Because python is cool ¯\_(ツ)_/¯.
+
+Incidentally, this exposes a much simpler way to run individual lint checks
+against individual modules, so it's useful beyond applying fixes.
+
+## Why?
+
+Lint is not allowed to modify source files directly via lint's `--apply-suggestions` flag.
+As a compromise, soong zips up the (potentially) modified sources and leaves them in an intermediate
+directory.  This script runs the lint, unpacks those files, and copies them back into the tree.
+
+## How do I run it?
+**WARNING: You probably want to commit/stash any changes to your working tree before doing this...**
+
+```
+source build/envsetup.sh
+lunch cf_x86_64_phone-userdebug # or any lunch target
+m lint_fix
+lint_fix -h
+```
+
+The script's help output explains things that are omitted here.
diff --git a/tools/lint/fix/soong_lint_fix.py b/tools/lint/fix/soong_lint_fix.py
new file mode 100644
index 0000000..cd4d778d
--- /dev/null
+++ b/tools/lint/fix/soong_lint_fix.py
@@ -0,0 +1,173 @@
+#  Copyright (C) 2022 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.
+
+import argparse
+import json
+import os
+import shutil
+import subprocess
+import sys
+import zipfile
+
+ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP")
+ANDROID_PRODUCT_OUT = os.environ.get("ANDROID_PRODUCT_OUT")
+PRODUCT_OUT = ANDROID_PRODUCT_OUT.removeprefix(f"{ANDROID_BUILD_TOP}/")
+
+SOONG_UI = "build/soong/soong_ui.bash"
+PATH_PREFIX = "out/soong/.intermediates"
+PATH_SUFFIX = "android_common/lint"
+FIX_ZIP = "suggested-fixes.zip"
+
+class SoongLintFix:
+    """
+    This class creates a command line tool that will
+    apply lint fixes to the platform via the necessary
+    combination of soong and shell commands.
+
+    It breaks up these operations into a few "private" methods
+    that are intentionally exposed so experimental code can tweak behavior.
+
+    The entry point, `run`, will apply lint fixes using the
+    intermediate `suggested-fixes` directory that soong creates during its
+    invocation of lint.
+
+    Basic usage:
+    ```
+    from soong_lint_fix import SoongLintFix
+
+    SoongLintFix().run()
+    ```
+    """
+    def __init__(self):
+        self._parser = _setup_parser()
+        self._args = None
+        self._kwargs = None
+        self._path = None
+        self._target = None
+
+
+    def run(self, additional_setup=None, custom_fix=None):
+        """
+        Run the script
+        """
+        self._setup()
+        self._find_module()
+        self._lint()
+
+        if not self._args.no_fix:
+            self._fix()
+
+        if self._args.print:
+            self._print()
+
+    def _setup(self):
+        self._args = self._parser.parse_args()
+        env = os.environ.copy()
+        if self._args.check:
+            env["ANDROID_LINT_CHECK"] = self._args.check
+        if self._args.lint_module:
+            env["ANDROID_LINT_CHECK_EXTRA_MODULES"] = self._args.lint_module
+
+        self._kwargs = {
+            "env": env,
+            "executable": "/bin/bash",
+            "shell": True,
+        }
+
+        os.chdir(ANDROID_BUILD_TOP)
+
+
+    def _find_module(self):
+        print("Refreshing soong modules...")
+        try:
+            os.mkdir(ANDROID_PRODUCT_OUT)
+        except OSError:
+            pass
+        subprocess.call(f"{SOONG_UI} --make-mode {PRODUCT_OUT}/module-info.json", **self._kwargs)
+        print("done.")
+
+        with open(f"{ANDROID_PRODUCT_OUT}/module-info.json") as f:
+            module_info = json.load(f)
+
+        if self._args.module not in module_info:
+            sys.exit(f"Module {self._args.module} not found!")
+
+        module_path = module_info[self._args.module]["path"][0]
+        print(f"Found module {module_path}/{self._args.module}.")
+
+        self._path = f"{PATH_PREFIX}/{module_path}/{self._args.module}/{PATH_SUFFIX}"
+        self._target = f"{self._path}/lint-report.txt"
+
+
+    def _lint(self):
+        print("Cleaning up any old lint results...")
+        try:
+            os.remove(f"{self._target}")
+            os.remove(f"{self._path}/{FIX_ZIP}")
+        except FileNotFoundError:
+            pass
+        print("done.")
+
+        print(f"Generating {self._target}")
+        subprocess.call(f"{SOONG_UI} --make-mode {self._target}", **self._kwargs)
+        print("done.")
+
+
+    def _fix(self):
+        print("Copying suggested fixes to the tree...")
+        with zipfile.ZipFile(f"{self._path}/{FIX_ZIP}") as zip:
+            for name in zip.namelist():
+                if name.startswith("out") or not name.endswith(".java"):
+                    continue
+                with zip.open(name) as src, open(f"{ANDROID_BUILD_TOP}/{name}", "wb") as dst:
+                    shutil.copyfileobj(src, dst)
+            print("done.")
+
+
+    def _print(self):
+        print("### lint-report.txt ###", end="\n\n")
+        with open(self._target, "r") as f:
+            print(f.read())
+
+
+def _setup_parser():
+    parser = argparse.ArgumentParser(description="""
+        This is a python script that applies lint fixes to the platform:
+        1. Set up the environment, etc.
+        2. Run lint on the specified target.
+        3. Copy the modified files, from soong's intermediate directory, back into the tree.
+
+        **Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first.
+        """, formatter_class=argparse.RawTextHelpFormatter)
+
+    parser.add_argument('module',
+                        help='The soong build module to run '
+                             '(e.g. framework-minus-apex or services.core.unboosted)')
+
+    parser.add_argument('--check',
+                        help='Which lint to run. Passed to the ANDROID_LINT_CHECK environment variable.')
+
+    parser.add_argument('--lint-module',
+                            help='Specific lint module to run. Passed to the ANDROID_LINT_CHECK_EXTRA_MODULES environment variable.')
+
+    parser.add_argument('--no-fix', action='store_true',
+                        help='Just build and run the lint, do NOT apply the fixes.')
+
+    parser.add_argument('--print', action='store_true',
+                        help='Print the contents of the generated lint-report.txt at the end.')
+
+    return parser
+
+if __name__ == "__main__":
+    SoongLintFix().run()
\ No newline at end of file
diff --git a/tools/lint/Android.bp b/tools/lint/framework/Android.bp
similarity index 68%
rename from tools/lint/Android.bp
rename to tools/lint/framework/Android.bp
index 2601041..30a6daa 100644
--- a/tools/lint/Android.bp
+++ b/tools/lint/framework/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 The Android Open Source Project
+// Copyright (C) 2022 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.
@@ -29,17 +29,14 @@
         "auto_service_annotations",
         "lint_api",
     ],
+    static_libs: [
+        "AndroidCommonLint",
+    ],
     kotlincflags: ["-Xjvm-default=all"],
 }
 
 java_test_host {
     name: "AndroidFrameworkLintCheckerTest",
-    // TODO(b/239881504): Since this test was written, Android
-    // Lint was updated, and now includes classes that were
-    // compiled for java 15. The soong build doesn't support
-    // java 15 yet, so we can't compile against "lint". Disable
-    // the test until java 15 is supported.
-    enabled: false,
     srcs: ["checks/src/test/java/**/*.kt"],
     static_libs: [
         "AndroidFrameworkLintChecker",
@@ -49,5 +46,19 @@
     ],
     test_options: {
         unit_test: true,
+        tradefed_options: [
+            {
+                // lint bundles in some classes that were built with older versions
+                // of libraries, and no longer load. Since tradefed tries to load
+                // all classes in the jar to look for tests, it crashes loading them.
+                // Exclude these classes from tradefed's search.
+                name: "exclude-paths",
+                value: "org/apache",
+            },
+            {
+                name: "exclude-paths",
+                value: "META-INF",
+            },
+        ],
     },
 }
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
new file mode 100644
index 0000000..935bade
--- /dev/null
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.google.android.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.android.lint.parcel.SaferParcelChecker
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+@Suppress("UnstableApiUsage")
+class AndroidFrameworkIssueRegistry : IssueRegistry() {
+    override val issues = listOf(
+        CallingIdentityTokenDetector.ISSUE_UNUSED_TOKEN,
+        CallingIdentityTokenDetector.ISSUE_NON_FINAL_TOKEN,
+        CallingIdentityTokenDetector.ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
+        CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
+        CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
+        CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY,
+        CallingIdentityTokenDetector.ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE,
+        CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
+        SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
+        // TODO: Currently crashes due to OOM issue
+        // PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
+        PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE,
+        PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD,
+    )
+
+    override val api: Int
+        get() = CURRENT_API
+
+    override val minApi: Int
+        get() = 8
+
+    override val vendor: Vendor = Vendor(
+        vendorName = "Android",
+        feedbackUrl = "http://b/issues/new?component=315013",
+        contact = "brufino@google.com"
+    )
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
similarity index 64%
rename from tools/lint/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
index 930378b..0c375c3 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
@@ -33,6 +33,7 @@
 import org.jetbrains.uast.UCallExpression
 import org.jetbrains.uast.UDeclarationsExpression
 import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UIfExpression
 import org.jetbrains.uast.ULocalVariable
 import org.jetbrains.uast.USimpleNameReferenceExpression
 import org.jetbrains.uast.UTryExpression
@@ -52,10 +53,10 @@
     private val tokensMap = mutableMapOf<String, Token>()
 
     override fun getApplicableUastTypes(): List<Class<out UElement?>> =
-            listOf(ULocalVariable::class.java, UCallExpression::class.java)
+        listOf(ULocalVariable::class.java, UCallExpression::class.java)
 
     override fun createUastHandler(context: JavaContext): UElementHandler =
-            TokenUastHandler(context)
+        TokenUastHandler(context)
 
     /** File analysis starts with a clear map */
     override fun beforeCheckFile(context: Context) {
@@ -70,9 +71,9 @@
     override fun afterCheckFile(context: Context) {
         for (token in tokensMap.values) {
             context.report(
-                    ISSUE_UNUSED_TOKEN,
-                    token.location,
-                    getIncidentMessageUnusedToken(token.variableName)
+                ISSUE_UNUSED_TOKEN,
+                token.location,
+                getIncidentMessageUnusedToken(token.variableName)
             )
         }
         tokensMap.clear()
@@ -96,9 +97,9 @@
             val variableName = node.getName()
             if (!node.isFinal) {
                 context.report(
-                        ISSUE_NON_FINAL_TOKEN,
-                        location,
-                        getIncidentMessageNonFinalToken(variableName)
+                    ISSUE_NON_FINAL_TOKEN,
+                    location,
+                    getIncidentMessageNonFinalToken(variableName)
                 )
             }
             // If there exists an unused variable with the same name in the map, we can imply that
@@ -106,9 +107,9 @@
             val oldToken = tokensMap[variableName]
             if (oldToken != null) {
                 context.report(
-                        ISSUE_UNUSED_TOKEN,
-                        oldToken.location,
-                        getIncidentMessageUnusedToken(oldToken.variableName)
+                    ISSUE_UNUSED_TOKEN,
+                    oldToken.location,
+                    getIncidentMessageUnusedToken(oldToken.variableName)
                 )
             }
             // If there exists a token in the same scope as the current new token, it means that
@@ -117,56 +118,84 @@
             val firstCallToken = findFirstTokenInScope(node)
             if (firstCallToken != null) {
                 context.report(
-                        ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
-                        createNestedLocation(firstCallToken, location),
-                        getIncidentMessageNestedClearIdentityCallsPrimary(
-                                firstCallToken.variableName,
-                                variableName
-                        )
+                    ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
+                    createNestedLocation(firstCallToken, location),
+                    getIncidentMessageNestedClearIdentityCallsPrimary(
+                        firstCallToken.variableName,
+                        variableName
+                    )
                 )
             }
             // If the next statement in the tree is not a try-finally statement, we need to report
             // the "clearCallingIdentity() is not followed by try-finally" issue
             val finallyClause = (getNextStatementOfLocalVariable(node) as? UTryExpression)
-                    ?.finallyClause
+                ?.finallyClause
             if (finallyClause == null) {
                 context.report(
-                        ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY,
-                        location,
-                        getIncidentMessageClearIdentityCallNotFollowedByTryFinally(variableName)
+                    ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY,
+                    location,
+                    getIncidentMessageClearIdentityCallNotFollowedByTryFinally(variableName)
                 )
             }
             tokensMap[variableName] = Token(
-                    variableName,
-                    node.sourcePsi?.getUseScope(),
-                    location,
-                    finallyClause
+                variableName,
+                node.sourcePsi?.getUseScope(),
+                location,
+                finallyClause
             )
         }
 
+        override fun visitCallExpression(node: UCallExpression) {
+            when {
+                isMethodCall(node, Method.BINDER_CLEAR_CALLING_IDENTITY) -> {
+                    checkClearCallingIdentityCall(node)
+                }
+                isMethodCall(node, Method.BINDER_RESTORE_CALLING_IDENTITY) -> {
+                    checkRestoreCallingIdentityCall(node)
+                }
+                isCallerAwareMethod(node) -> checkCallerAwareMethod(node)
+            }
+        }
+
+        private fun checkClearCallingIdentityCall(node: UCallExpression) {
+            var firstNonQualifiedParent = getFirstNonQualifiedParent(node)
+            // if the call expression is inside a ternary, and the ternary is assigned
+            // to a variable, then we are still technically assigning
+            // any result of clearCallingIdentity to a variable
+            if (firstNonQualifiedParent is UIfExpression && firstNonQualifiedParent.isTernary) {
+                firstNonQualifiedParent = firstNonQualifiedParent.uastParent
+            }
+            if (firstNonQualifiedParent !is ULocalVariable) {
+                context.report(
+                    ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE,
+                    context.getLocation(node),
+                    getIncidentMessageResultOfClearIdentityCallNotStoredInVariable(
+                        node.getQualifiedParentOrThis().asRenderString()
+                    )
+                )
+            }
+        }
+
+        private fun checkCallerAwareMethod(node: UCallExpression) {
+            val token = findFirstTokenInScope(node)
+            if (token != null) {
+                context.report(
+                    ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
+                    context.getLocation(node),
+                    getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity(
+                        token.variableName,
+                        node.asRenderString()
+                    )
+                )
+            }
+        }
+
         /**
-         * For every method():
-         * - Checks use of caller-aware methods issue
-         * For every call of Binder.restoreCallingIdentity(token):
          * - Checks for restoreCallingIdentity() not in the finally block issue
          * - Removes token from tokensMap if token is within the scope of the method
          */
-        override fun visitCallExpression(node: UCallExpression) {
-            val token = findFirstTokenInScope(node)
-            if (isCallerAwareMethod(node) && token != null) {
-                context.report(
-                        ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
-                        context.getLocation(node),
-                        getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity(
-                                token.variableName,
-                                node.asRenderString()
-                        )
-                )
-                return
-            }
-            if (!isMethodCall(node, Method.BINDER_RESTORE_CALLING_IDENTITY)) return
-            val first = node.valueArguments[0].skipParenthesizedExprDown()
-            val arg = first as? USimpleNameReferenceExpression ?: return
+        private fun checkRestoreCallingIdentityCall(node: UCallExpression) {
+            val arg = node.valueArguments[0] as? USimpleNameReferenceExpression ?: return
             val variableName = arg.identifier
             val originalScope = tokensMap[variableName]?.scope ?: return
             val psi = arg.sourcePsi ?: return
@@ -174,26 +203,31 @@
             // token declaration. If not within the scope, no action is needed because the token is
             // irrelevant i.e. not in the same scope or was not declared with clearCallingIdentity()
             if (!PsiSearchScopeUtil.isInScope(originalScope, psi)) return
-            // - We do not report "restore identity call not in finally" issue when there is no
+            // We do not report "restore identity call not in finally" issue when there is no
             // finally block because that case is already handled by "clear identity call not
             // followed by try-finally" issue
-            // - UCallExpression can be a child of UQualifiedReferenceExpression, i.e.
-            // receiver.selector, so to get the call's immediate parent we need to get the topmost
-            // parent qualified reference expression and access its parent
             if (tokensMap[variableName]?.finallyBlock != null &&
-                    skipParenthesizedExprUp(node.getQualifiedParentOrThis().uastParent) !=
-                        tokensMap[variableName]?.finallyBlock) {
+                getFirstNonQualifiedParent(node) !=
+                tokensMap[variableName]?.finallyBlock
+            ) {
                 context.report(
-                        ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
-                        context.getLocation(node),
-                        getIncidentMessageRestoreIdentityCallNotInFinallyBlock(variableName)
+                    ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
+                    context.getLocation(node),
+                    getIncidentMessageRestoreIdentityCallNotInFinallyBlock(variableName)
                 )
             }
             tokensMap.remove(variableName)
         }
 
+        private fun getFirstNonQualifiedParent(expression: UCallExpression): UElement? {
+            // UCallExpression can be a child of UQualifiedReferenceExpression, i.e.
+            // receiver.selector, so to get the call's immediate parent we need to get the topmost
+            // parent qualified reference expression and access its parent
+            return skipParenthesizedExprUp(expression.getQualifiedParentOrThis().uastParent)
+        }
+
         private fun isCallerAwareMethod(expression: UCallExpression): Boolean =
-                callerAwareMethods.any { method -> isMethodCall(expression, method) }
+            callerAwareMethods.any { method -> isMethodCall(expression, method) }
 
         private fun isMethodCall(
             expression: UCallExpression,
@@ -201,12 +235,12 @@
         ): Boolean {
             val psiMethod = expression.resolve() ?: return false
             return psiMethod.getName() == method.methodName &&
-                    context.evaluator.methodMatches(
-                            psiMethod,
-                            method.className,
-                            /* allowInherit */ true,
-                            *method.args
-                    )
+                context.evaluator.methodMatches(
+                    psiMethod,
+                    method.className,
+                    /* allowInherit */ true,
+                    *method.args
+                )
         }
 
         /**
@@ -255,7 +289,7 @@
                 return declarations[indexInDeclarations + 1]
             }
             val enclosingBlock = node
-                    .getParentOfType<UBlockExpression>(strict = true) ?: return null
+                .getParentOfType<UBlockExpression>(strict = true) ?: return null
             val expressions = enclosingBlock.expressions
             val indexInBlock = expressions.indexOf(declarationsExpression as UElement)
             return if (indexInBlock == -1) null else expressions.getOrNull(indexInBlock + 1)
@@ -301,12 +335,12 @@
         secondCallTokenLocation: Location
     ): Location {
         return cloneLocation(secondCallTokenLocation)
-                .withSecondary(
-                        cloneLocation(firstCallToken.location),
-                        getIncidentMessageNestedClearIdentityCallsSecondary(
-                                firstCallToken.variableName
-                        )
+            .withSecondary(
+                cloneLocation(firstCallToken.location),
+                getIncidentMessageNestedClearIdentityCallsSecondary(
+                    firstCallToken.variableName
                 )
+            )
     }
 
     private fun cloneLocation(location: Location): Location {
@@ -347,20 +381,20 @@
         const val CLASS_USER_HANDLE = "android.os.UserHandle"
 
         private val callerAwareMethods = listOf(
-                Method.BINDER_GET_CALLING_PID,
-                Method.BINDER_GET_CALLING_UID,
-                Method.BINDER_GET_CALLING_UID_OR_THROW,
-                Method.BINDER_GET_CALLING_USER_HANDLE,
-                Method.USER_HANDLE_GET_CALLING_APP_ID,
-                Method.USER_HANDLE_GET_CALLING_USER_ID
+            Method.BINDER_GET_CALLING_PID,
+            Method.BINDER_GET_CALLING_UID,
+            Method.BINDER_GET_CALLING_UID_OR_THROW,
+            Method.BINDER_GET_CALLING_USER_HANDLE,
+            Method.USER_HANDLE_GET_CALLING_APP_ID,
+            Method.USER_HANDLE_GET_CALLING_USER_ID
         )
 
         /** Issue: unused token from Binder.clearCallingIdentity() */
         @JvmField
         val ISSUE_UNUSED_TOKEN: Issue = Issue.create(
-                id = "UnusedTokenOfOriginalCallingIdentity",
-                briefDescription = "Unused token of Binder.clearCallingIdentity()",
-                explanation = """
+            id = "UnusedTokenOfOriginalCallingIdentity",
+            briefDescription = "Unused token of Binder.clearCallingIdentity()",
+            explanation = """
                     You cleared the original calling identity with \
                     `Binder.clearCallingIdentity()`, but have not used the returned token to \
                     restore the identity.
@@ -370,26 +404,26 @@
 
                     `token` is the result of `Binder.clearCallingIdentity()`
                     """,
-                category = Category.SECURITY,
-                priority = 6,
-                severity = Severity.WARNING,
-                implementation = Implementation(
-                        CallingIdentityTokenDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
-                )
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                CallingIdentityTokenDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            )
         )
 
         private fun getIncidentMessageUnusedToken(variableName: String) = "`$variableName` has " +
-                "not been used to restore the calling identity. Introduce a `try`-`finally` " +
-                "after the declaration and call `Binder.restoreCallingIdentity($variableName)` " +
-                "in `finally` or remove `$variableName`."
+            "not been used to restore the calling identity. Introduce a `try`-`finally` " +
+            "after the declaration and call `Binder.restoreCallingIdentity($variableName)` " +
+            "in `finally` or remove `$variableName`."
 
         /** Issue: non-final token from Binder.clearCallingIdentity() */
         @JvmField
         val ISSUE_NON_FINAL_TOKEN: Issue = Issue.create(
-                id = "NonFinalTokenOfOriginalCallingIdentity",
-                briefDescription = "Non-final token of Binder.clearCallingIdentity()",
-                explanation = """
+            id = "NonFinalTokenOfOriginalCallingIdentity",
+            briefDescription = "Non-final token of Binder.clearCallingIdentity()",
+            explanation = """
                     You cleared the original calling identity with \
                     `Binder.clearCallingIdentity()`, but have not made the returned token `final`.
 
@@ -397,47 +431,47 @@
                     which can cause problems when restoring the identity with \
                     `Binder.restoreCallingIdentity(token)`.
                     """,
-                category = Category.SECURITY,
-                priority = 6,
-                severity = Severity.WARNING,
-                implementation = Implementation(
-                        CallingIdentityTokenDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
-                )
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                CallingIdentityTokenDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            )
         )
 
         private fun getIncidentMessageNonFinalToken(variableName: String) = "`$variableName` is " +
-                "a non-final token from `Binder.clearCallingIdentity()`. Add `final` keyword to " +
-                "`$variableName`."
+            "a non-final token from `Binder.clearCallingIdentity()`. Add `final` keyword to " +
+            "`$variableName`."
 
         /** Issue: nested calls of Binder.clearCallingIdentity() */
         @JvmField
         val ISSUE_NESTED_CLEAR_IDENTITY_CALLS: Issue = Issue.create(
-                id = "NestedClearCallingIdentityCalls",
-                briefDescription = "Nested calls of Binder.clearCallingIdentity()",
-                explanation = """
+            id = "NestedClearCallingIdentityCalls",
+            briefDescription = "Nested calls of Binder.clearCallingIdentity()",
+            explanation = """
                     You cleared the original calling identity with \
                     `Binder.clearCallingIdentity()` twice without restoring identity with the \
                     result of the first call.
 
                     Make sure to restore the identity after each clear identity call.
                     """,
-                category = Category.SECURITY,
-                priority = 6,
-                severity = Severity.WARNING,
-                implementation = Implementation(
-                        CallingIdentityTokenDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
-                )
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                CallingIdentityTokenDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            )
         )
 
         private fun getIncidentMessageNestedClearIdentityCallsPrimary(
             firstCallVariableName: String,
             secondCallVariableName: String
         ): String = "The calling identity has already been cleared and returned into " +
-                "`$firstCallVariableName`. Move `$secondCallVariableName` declaration after " +
-                "restoring the calling identity with " +
-                "`Binder.restoreCallingIdentity($firstCallVariableName)`."
+            "`$firstCallVariableName`. Move `$secondCallVariableName` declaration after " +
+            "restoring the calling identity with " +
+            "`Binder.restoreCallingIdentity($firstCallVariableName)`."
 
         private fun getIncidentMessageNestedClearIdentityCallsSecondary(
             firstCallVariableName: String
@@ -446,10 +480,10 @@
         /** Issue: Binder.clearCallingIdentity() is not followed by `try-finally` statement */
         @JvmField
         val ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY: Issue = Issue.create(
-                id = "ClearIdentityCallNotFollowedByTryFinally",
-                briefDescription = "Binder.clearCallingIdentity() is not followed by try-finally " +
-                        "statement",
-                explanation = """
+            id = "ClearIdentityCallNotFollowedByTryFinally",
+            briefDescription = "Binder.clearCallingIdentity() is not followed by try-finally " +
+                "statement",
+            explanation = """
                     You cleared the original calling identity with \
                     `Binder.clearCallingIdentity()`, but the next statement is not a `try` \
                     statement.
@@ -472,30 +506,30 @@
                     code with your identity that was originally intended to run with the calling \
                     application's identity.
                     """,
-                category = Category.SECURITY,
-                priority = 6,
-                severity = Severity.WARNING,
-                implementation = Implementation(
-                        CallingIdentityTokenDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
-                )
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                CallingIdentityTokenDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            )
         )
 
         private fun getIncidentMessageClearIdentityCallNotFollowedByTryFinally(
             variableName: String
         ): String = "You cleared the calling identity and returned the result into " +
-                "`$variableName`, but the next statement is not a `try`-`finally` statement. " +
-                "Define a `try`-`finally` block after `$variableName` declaration to ensure a " +
-                "safe restore of the calling identity by calling " +
-                "`Binder.restoreCallingIdentity($variableName)` and making it an immediate child " +
-                "of the `finally` block."
+            "`$variableName`, but the next statement is not a `try`-`finally` statement. " +
+            "Define a `try`-`finally` block after `$variableName` declaration to ensure a " +
+            "safe restore of the calling identity by calling " +
+            "`Binder.restoreCallingIdentity($variableName)` and making it an immediate child " +
+            "of the `finally` block."
 
         /** Issue: Binder.restoreCallingIdentity() is not in finally block */
         @JvmField
         val ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK: Issue = Issue.create(
-                id = "RestoreIdentityCallNotInFinallyBlock",
-                briefDescription = "Binder.restoreCallingIdentity() is not in finally block",
-                explanation = """
+            id = "RestoreIdentityCallNotInFinallyBlock",
+            briefDescription = "Binder.restoreCallingIdentity() is not in finally block",
+            explanation = """
                     You are restoring the original calling identity with \
                     `Binder.restoreCallingIdentity()`, but the call is not an immediate child of \
                     the `finally` block of the `try` statement.
@@ -516,28 +550,28 @@
                     `finally` block, you may run code with your identity that was originally \
                     intended to run with the calling application's identity.
                     """,
-                category = Category.SECURITY,
-                priority = 6,
-                severity = Severity.WARNING,
-                implementation = Implementation(
-                        CallingIdentityTokenDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
-                )
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                CallingIdentityTokenDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            )
         )
 
         private fun getIncidentMessageRestoreIdentityCallNotInFinallyBlock(
             variableName: String
         ): String = "`Binder.restoreCallingIdentity($variableName)` is not an immediate child of " +
-                "the `finally` block of the try statement after `$variableName` declaration. " +
-                        "Surround the call with `finally` block and call it unconditionally."
+            "the `finally` block of the try statement after `$variableName` declaration. " +
+            "Surround the call with `finally` block and call it unconditionally."
 
         /** Issue: Use of caller-aware methods after Binder.clearCallingIdentity() */
         @JvmField
         val ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY: Issue = Issue.create(
-                id = "UseOfCallerAwareMethodsWithClearedIdentity",
-                briefDescription = "Use of caller-aware methods after " +
-                        "Binder.clearCallingIdentity()",
-                explanation = """
+            id = "UseOfCallerAwareMethodsWithClearedIdentity",
+            briefDescription = "Use of caller-aware methods after " +
+                "Binder.clearCallingIdentity()",
+            explanation = """
                     You cleared the original calling identity with \
                     `Binder.clearCallingIdentity()`, but used one of the methods below before \
                     restoring the identity. These methods will use your own identity instead of \
@@ -556,22 +590,59 @@
                     UserHandle.getCallingUserId()
                     ```
                     """,
-                category = Category.SECURITY,
-                priority = 6,
-                severity = Severity.WARNING,
-                implementation = Implementation(
-                        CallingIdentityTokenDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
-                )
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                CallingIdentityTokenDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            )
         )
 
         private fun getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity(
             variableName: String,
             methodName: String
         ): String = "You cleared the original identity with `Binder.clearCallingIdentity()` " +
-                "and returned into `$variableName`, so `$methodName` will be using your own " +
-                "identity instead of the caller's. Either explicitly query your own identity or " +
-                "move it after restoring the identity with " +
-                "`Binder.restoreCallingIdentity($variableName)`."
+            "and returned into `$variableName`, so `$methodName` will be using your own " +
+            "identity instead of the caller's. Either explicitly query your own identity or " +
+            "move it after restoring the identity with " +
+            "`Binder.restoreCallingIdentity($variableName)`."
+
+        /** Issue: Result of Binder.clearCallingIdentity() is not stored in a variable */
+        @JvmField
+        val ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE: Issue = Issue.create(
+            id = "ResultOfClearIdentityCallNotStoredInVariable",
+            briefDescription = "Result of Binder.clearCallingIdentity() is not stored in a " +
+                "variable",
+            explanation = """
+           You cleared the original calling identity with \
+           `Binder.clearCallingIdentity()`, but did not store the result of the method \
+           call in a variable. You need to store the result in a variable and restore it later.
+
+           Use the following pattern for running operations with your own identity:
+
+           ```
+           final long token = Binder.clearCallingIdentity();
+           try {
+               // Code using your own identity
+           } finally {
+               Binder.restoreCallingIdentity(token);
+           }
+           ```
+           """,
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                CallingIdentityTokenDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            )
+        )
+
+        private fun getIncidentMessageResultOfClearIdentityCallNotStoredInVariable(
+            methodName: String
+        ): String = "You cleared the original identity with `$methodName` but did not store the " +
+            "result in a variable. You need to store the result in a variable and restore it " +
+            "later."
     }
 }
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
new file mode 100644
index 0000000..48540b1d
--- /dev/null
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint
+
+import com.android.tools.lint.client.api.UastParser
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Context
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.android.tools.lint.detector.api.interprocedural.CallGraph
+import com.android.tools.lint.detector.api.interprocedural.CallGraphResult
+import com.android.tools.lint.detector.api.interprocedural.searchForPaths
+import com.intellij.psi.PsiAnonymousClass
+import com.intellij.psi.PsiMethod
+import java.util.LinkedList
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UParameter
+import org.jetbrains.uast.USimpleNameReferenceExpression
+import org.jetbrains.uast.visitor.AbstractUastVisitor
+
+/**
+ * A lint checker to detect potential package visibility issues for system's APIs. APIs working
+ * in the system_server and taking the package name as a parameter may have chance to reveal
+ * package existence status on the device, and break the
+ * <a href="https://developer.android.com/about/versions/11/privacy/package-visibility">
+ * Package Visibility</a> that we introduced in Android 11.
+ * <p>
+ * Take an example of the API `boolean setFoo(String packageName)`, a malicious app may have chance
+ * to detect package existence state on the device from the result of the API, if there is no
+ * package visibility filtering rule or uid identify checks applying to the parameter of the
+ * package name.
+ */
+class PackageVisibilityDetector : Detector(), SourceCodeScanner {
+
+    // Enables call graph analysis
+    override fun isCallGraphRequired(): Boolean = true
+
+    override fun analyzeCallGraph(
+        context: Context,
+        callGraph: CallGraphResult
+    ) {
+        val systemServerApiNodes = callGraph.callGraph.nodes.filter(::isSystemServerApi)
+        val sinkMethodNodes = callGraph.callGraph.nodes.filter {
+            // TODO(b/228285232): Remove enforce permission sink methods
+            isNodeInList(it, ENFORCE_PERMISSION_METHODS) || isNodeInList(it, APPOPS_METHODS)
+        }
+        val parser = context.client.getUastParser(context.project)
+        analyzeApisContainPackageNameParameters(
+            context, parser, systemServerApiNodes, sinkMethodNodes)
+    }
+
+    /**
+     * Looking for API contains package name parameters, report the lint issue if the API does not
+     * invoke any sink methods.
+     */
+    private fun analyzeApisContainPackageNameParameters(
+        context: Context,
+        parser: UastParser,
+        systemServerApiNodes: List<CallGraph.Node>,
+        sinkMethodNodes: List<CallGraph.Node>
+    ) {
+        for (apiNode in systemServerApiNodes) {
+            val apiMethod = apiNode.getUMethod() ?: continue
+            val pkgNameParamIndexes = apiMethod.uastParameters.mapIndexedNotNull { index, param ->
+                if (Parameter(param) in PACKAGE_NAME_PATTERNS && apiNode.isArgumentInUse(index)) {
+                    index
+                } else {
+                    null
+                }
+            }.takeIf(List<Int>::isNotEmpty) ?: continue
+
+            for (pkgNameParamIndex in pkgNameParamIndexes) {
+                // Trace the call path of the method's argument, pass the lint checks if a sink
+                // method is found
+                if (traceArgumentCallPath(
+                        apiNode, pkgNameParamIndex, PACKAGE_NAME_SINK_METHOD_LIST)) {
+                    continue
+                }
+                // Pass the check if one of the sink methods is invoked
+                if (hasValidPath(
+                        searchForPaths(
+                            sources = listOf(apiNode),
+                            isSink = { it in sinkMethodNodes },
+                            getNeighbors = { node -> node.edges.map { it.node!! } }
+                        )
+                    )
+                ) continue
+
+                // Report issue
+                val reportElement = apiMethod.uastParameters[pkgNameParamIndex] as UElement
+                val location = parser.createLocation(reportElement)
+                context.report(
+                    ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
+                    location,
+                    getMsgPackageNameNoPackageVisibilityFilters(apiMethod, pkgNameParamIndex)
+                )
+            }
+        }
+    }
+
+    /**
+     * Returns {@code true} if the method associated with the given node is a system server's
+     * public API that extends from Stub class.
+     */
+    private fun isSystemServerApi(
+        node: CallGraph.Node
+    ): Boolean {
+        val method = node.getUMethod() ?: return false
+        if (!method.hasModifierProperty("public") ||
+            method.uastBody == null ||
+            method.containingClass is PsiAnonymousClass) {
+            return false
+        }
+        val className = method.containingClass?.qualifiedName ?: return false
+        if (!className.startsWith(SYSTEM_PACKAGE_PREFIX)) {
+            return false
+        }
+        return (method.containingClass ?: return false).supers
+            .filter { it.name == CLASS_STUB }
+            .filter { it.qualifiedName !in BYPASS_STUBS }
+            .any { it.findMethodBySignature(method, /* checkBases */ true) != null }
+    }
+
+    /**
+     * Returns {@code true} if the list contains the node of the call graph.
+     */
+    private fun isNodeInList(
+        node: CallGraph.Node,
+        filters: List<Method>
+    ): Boolean {
+        val method = node.getUMethod() ?: return false
+        return Method(method) in filters
+    }
+
+    /**
+     * Trace the call paths of the argument of the method in the start entry. Return {@code true}
+     * if one of methods in the sink call list is invoked.
+     * Take an example of the call path:
+     * foo(packageName) -> a(packageName) -> b(packageName) -> filterAppAccess()
+     * It returns {@code true} if the filterAppAccess() is in the sink call list.
+     */
+    private fun traceArgumentCallPath(
+        apiNode: CallGraph.Node,
+        pkgNameParamIndex: Int,
+        sinkList: List<Method>
+    ): Boolean {
+        val startEntry = TraceEntry(apiNode, pkgNameParamIndex)
+        val traceQueue = LinkedList<TraceEntry>().apply { add(startEntry) }
+        val allVisits = mutableSetOf<TraceEntry>().apply { add(startEntry) }
+        while (!traceQueue.isEmpty()) {
+            val entry = traceQueue.poll()
+            val entryNode = entry.node
+            val entryMethod = entryNode.getUMethod() ?: continue
+            val entryArgumentName = entryMethod.uastParameters[entry.argumentIndex].name
+            for (outEdge in entryNode.edges) {
+                val outNode = outEdge.node ?: continue
+                val outMethod = outNode.getUMethod() ?: continue
+                val outArgumentIndex =
+                    outEdge.call?.findArgumentIndex(
+                        entryArgumentName, outMethod.uastParameters.size)
+                val sinkMethod = findInSinkList(outMethod, sinkList)
+                if (sinkMethod == null) {
+                    if (outArgumentIndex == null) {
+                        // Path is not relevant to the sink method and argument
+                        continue
+                    }
+                    // Path is relevant to the argument, add a new trace entry if never visit before
+                    val newEntry = TraceEntry(outNode, outArgumentIndex)
+                    if (newEntry !in allVisits) {
+                        traceQueue.add(newEntry)
+                        allVisits.add(newEntry)
+                    }
+                    continue
+                }
+                if (sinkMethod.matchArgument && outArgumentIndex == null) {
+                    // The sink call is required to match the argument, but not found
+                    continue
+                }
+                if (sinkMethod.checkCaller &&
+                    entryMethod.isInClearCallingIdentityScope(outEdge.call!!)) {
+                    // The sink call is in the scope of Binder.clearCallingIdentify
+                    continue
+                }
+                // A sink method is matched
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Returns the UMethod associated with the given node of call graph.
+     */
+    private fun CallGraph.Node.getUMethod(): UMethod? = this.target.element as? UMethod
+
+    /**
+     * Returns the system module name (e.g. com.android.server.pm) of the method of the
+     * call graph node.
+     */
+    private fun CallGraph.Node.getModuleName(): String? {
+        val method = getUMethod() ?: return null
+        val className = method.containingClass?.qualifiedName ?: return null
+        if (!className.startsWith(SYSTEM_PACKAGE_PREFIX)) {
+            return null
+        }
+        val dotPos = className.indexOf(".", SYSTEM_PACKAGE_PREFIX.length)
+        if (dotPos == -1) {
+            return SYSTEM_PACKAGE_PREFIX
+        }
+        return className.substring(0, dotPos)
+    }
+
+    /**
+     * Return {@code true} if the argument in the method's body is in-use.
+     */
+    private fun CallGraph.Node.isArgumentInUse(argIndex: Int): Boolean {
+        val method = getUMethod() ?: return false
+        val argumentName = method.uastParameters[argIndex].name
+        var foundArg = false
+        val methodVisitor = object : AbstractUastVisitor() {
+            override fun visitSimpleNameReferenceExpression(
+                node: USimpleNameReferenceExpression
+            ): Boolean {
+                if (node.identifier == argumentName) {
+                    foundArg = true
+                }
+                return true
+            }
+        }
+        method.uastBody?.accept(methodVisitor)
+        return foundArg
+    }
+
+    /**
+     * Given an argument name, returns the index of argument in the call expression.
+     */
+    private fun UCallExpression.findArgumentIndex(
+        argumentName: String,
+        parameterSize: Int
+    ): Int? {
+        if (valueArgumentCount == 0 || parameterSize == 0) {
+            return null
+        }
+        var match = false
+        val argVisitor = object : AbstractUastVisitor() {
+            override fun visitSimpleNameReferenceExpression(
+                node: USimpleNameReferenceExpression
+            ): Boolean {
+                if (node.identifier == argumentName) {
+                    match = true
+                }
+                return true
+            }
+            override fun visitCallExpression(node: UCallExpression): Boolean {
+                return true
+            }
+        }
+        valueArguments.take(parameterSize).forEachIndexed { index, argument ->
+            argument.accept(argVisitor)
+            if (match) {
+                return index
+            }
+        }
+        return null
+    }
+
+    /**
+     * Given a UMethod, returns a method from the sink method list.
+     */
+    private fun findInSinkList(
+        uMethod: UMethod,
+        sinkCallList: List<Method>
+    ): Method? {
+        return sinkCallList.find {
+            it == Method(uMethod) ||
+                    it == Method(uMethod.containingClass?.qualifiedName ?: "", "*")
+        }
+    }
+
+    /**
+     * Returns {@code true} if the call expression is in the scope of the
+     * Binder.clearCallingIdentify.
+     */
+    private fun UMethod.isInClearCallingIdentityScope(call: UCallExpression): Boolean {
+        var isInScope = false
+        val methodVisitor = object : AbstractUastVisitor() {
+            private var clearCallingIdentity = 0
+            override fun visitCallExpression(node: UCallExpression): Boolean {
+                if (call == node && clearCallingIdentity != 0) {
+                    isInScope = true
+                    return true
+                }
+                val visitMethod = Method(node.resolve() ?: return false)
+                if (visitMethod == METHOD_CLEAR_CALLING_IDENTITY) {
+                    clearCallingIdentity++
+                } else if (visitMethod == METHOD_RESTORE_CALLING_IDENTITY) {
+                    clearCallingIdentity--
+                }
+                return false
+            }
+        }
+        accept(methodVisitor)
+        return isInScope
+    }
+
+    /**
+     * Checks the module name of the start node and the last node that invokes the sink method
+     * (e.g. checkPermission) in a path, returns {@code true} if one of the paths has the same
+     * module name for both nodes.
+     */
+    private fun hasValidPath(paths: Collection<List<CallGraph.Node>>): Boolean {
+        for (pathNodes in paths) {
+            if (pathNodes.size < VALID_CALL_PATH_NODES_SIZE) {
+                continue
+            }
+            val startModule = pathNodes[0].getModuleName() ?: continue
+            val lastCallModule = pathNodes[pathNodes.size - 2].getModuleName() ?: continue
+            if (startModule == lastCallModule) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * A data class to represent the method.
+     */
+    private data class Method(
+        val clazz: String,
+        val name: String
+    ) {
+        // Used by traceArgumentCallPath to indicate that the method is required to match the
+        // argument name
+        var matchArgument = true
+
+        // Used by traceArgumentCallPath to indicate that the method is required to check whether
+        // the Binder.clearCallingIdentity is invoked.
+        var checkCaller = false
+
+        constructor(
+            clazz: String,
+            name: String,
+            matchArgument: Boolean = true,
+            checkCaller: Boolean = false
+        ) : this(clazz, name) {
+            this.matchArgument = matchArgument
+            this.checkCaller = checkCaller
+        }
+
+        constructor(
+            method: PsiMethod
+        ) : this(method.containingClass?.qualifiedName ?: "", method.name)
+
+        constructor(
+            method: com.google.android.lint.model.Method
+        ) : this(method.clazz, method.name)
+    }
+
+    /**
+     * A data class to represent the parameter of the method. The parameter name is converted to
+     * lower case letters for comparison.
+     */
+    private data class Parameter private constructor(
+        val typeName: String,
+        val parameterName: String
+    ) {
+        constructor(uParameter: UParameter) : this(
+            uParameter.type.canonicalText,
+            uParameter.name.lowercase()
+        )
+
+        companion object {
+            fun create(typeName: String, parameterName: String) =
+                Parameter(typeName, parameterName.lowercase())
+        }
+    }
+
+    /**
+     * A data class wraps a method node of the call graph and an index that indicates an
+     * argument of the method to record call trace information.
+     */
+    private data class TraceEntry(
+        val node: CallGraph.Node,
+        val argumentIndex: Int
+    )
+
+    companion object {
+        private const val SYSTEM_PACKAGE_PREFIX = "com.android.server."
+        // A valid call path list needs to contain a start node and a sink node
+        private const val VALID_CALL_PATH_NODES_SIZE = 2
+
+        private const val CLASS_STRING = "java.lang.String"
+        private const val CLASS_PACKAGE_MANAGER = "android.content.pm.PackageManager"
+        private const val CLASS_IPACKAGE_MANAGER = "android.content.pm.IPackageManager"
+        private const val CLASS_APPOPS_MANAGER = "android.app.AppOpsManager"
+        private const val CLASS_BINDER = "android.os.Binder"
+        private const val CLASS_PACKAGE_MANAGER_INTERNAL =
+            "android.content.pm.PackageManagerInternal"
+
+        // Patterns of package name parameter
+        private val PACKAGE_NAME_PATTERNS = setOf(
+            Parameter.create(CLASS_STRING, "packageName"),
+            Parameter.create(CLASS_STRING, "callingPackage"),
+            Parameter.create(CLASS_STRING, "callingPackageName"),
+            Parameter.create(CLASS_STRING, "pkgName"),
+            Parameter.create(CLASS_STRING, "callingPkg"),
+            Parameter.create(CLASS_STRING, "pkg")
+        )
+
+        // Package manager APIs
+        private val PACKAGE_NAME_SINK_METHOD_LIST = listOf(
+            Method(CLASS_PACKAGE_MANAGER_INTERNAL, "filterAppAccess", matchArgument = false),
+            Method(CLASS_PACKAGE_MANAGER_INTERNAL, "canQueryPackage"),
+            Method(CLASS_PACKAGE_MANAGER_INTERNAL, "isSameApp"),
+            Method(CLASS_PACKAGE_MANAGER, "*", checkCaller = true),
+            Method(CLASS_IPACKAGE_MANAGER, "*", checkCaller = true),
+            Method(CLASS_PACKAGE_MANAGER, "getPackagesForUid", matchArgument = false),
+            Method(CLASS_IPACKAGE_MANAGER, "getPackagesForUid", matchArgument = false)
+        )
+
+        // AppOps APIs which include uid and package visibility filters checks
+        private val APPOPS_METHODS = listOf(
+            Method(CLASS_APPOPS_MANAGER, "noteOp"),
+            Method(CLASS_APPOPS_MANAGER, "noteOpNoThrow"),
+            Method(CLASS_APPOPS_MANAGER, "noteOperation"),
+            Method(CLASS_APPOPS_MANAGER, "noteProxyOp"),
+            Method(CLASS_APPOPS_MANAGER, "noteProxyOpNoThrow"),
+            Method(CLASS_APPOPS_MANAGER, "startOp"),
+            Method(CLASS_APPOPS_MANAGER, "startOpNoThrow"),
+            Method(CLASS_APPOPS_MANAGER, "FinishOp"),
+            Method(CLASS_APPOPS_MANAGER, "finishProxyOp"),
+            Method(CLASS_APPOPS_MANAGER, "checkPackage")
+        )
+
+        // Enforce permission APIs
+        private val ENFORCE_PERMISSION_METHODS =
+                com.google.android.lint.ENFORCE_PERMISSION_METHODS
+                        .map(PackageVisibilityDetector::Method)
+
+        private val BYPASS_STUBS = listOf(
+            "android.content.pm.IPackageDataObserver.Stub",
+            "android.content.pm.IPackageDeleteObserver.Stub",
+            "android.content.pm.IPackageDeleteObserver2.Stub",
+            "android.content.pm.IPackageInstallObserver2.Stub",
+            "com.android.internal.app.IAppOpsCallback.Stub",
+
+            // TODO(b/228285637): Do not bypass PackageManagerService API
+            "android.content.pm.IPackageManager.Stub",
+            "android.content.pm.IPackageManagerNative.Stub"
+        )
+
+        private val METHOD_CLEAR_CALLING_IDENTITY =
+            Method(CLASS_BINDER, "clearCallingIdentity")
+        private val METHOD_RESTORE_CALLING_IDENTITY =
+            Method(CLASS_BINDER, "restoreCallingIdentity")
+
+        private fun getMsgPackageNameNoPackageVisibilityFilters(
+            method: UMethod,
+            argumentIndex: Int
+        ): String = "Api: ${method.name} contains a package name parameter: " +
+                "${method.uastParameters[argumentIndex].name} does not apply " +
+                "package visibility filtering rules."
+
+        private val EXPLANATION = """
+            APIs working in the system_server and taking the package name as a parameter may have
+            chance to reveal package existence status on the device, and break the package
+            visibility that we introduced in Android 11.
+            (https://developer.android.com/about/versions/11/privacy/package-visibility)
+
+            Take an example of the API `boolean setFoo(String packageName)`, a malicious app may
+            have chance to get package existence state on the device from the result of the API,
+            if there is no package visibility filtering rule or uid identify checks applying to
+            the parameter of the package name.
+
+            To resolve it, you could apply package visibility filtering rules to the package name
+            via PackageManagerInternal.filterAppAccess API, before starting to use the package name.
+            If the parameter is a calling package name, use the PackageManager API such as
+            PackageManager.getPackagesForUid to verify the calling identify.
+            """
+
+        val ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS = Issue.create(
+            id = "ApiMightLeakAppVisibility",
+            briefDescription = "Api takes package name parameter doesn't apply " +
+                    "package visibility filters",
+            explanation = EXPLANATION,
+            category = Category.SECURITY,
+            priority = 1,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                PackageVisibilityDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            )
+        )
+    }
+}
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
new file mode 100644
index 0000000..e12ec3d
--- /dev/null
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.android.tools.lint.detector.api.getUMethod
+import com.intellij.psi.PsiType
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UIfExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UQualifiedReferenceExpression
+import org.jetbrains.uast.UReturnExpression
+import org.jetbrains.uast.getContainingUMethod
+
+/**
+ * Stops incorrect usage of {@link PermissionMethod}
+ * TODO: add tests once re-enabled (b/240445172, b/247542171)
+ */
+class PermissionMethodDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableUastTypes(): List<Class<out UElement>> =
+        listOf(UAnnotation::class.java, UMethod::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler =
+        PermissionMethodHandler(context)
+
+    private inner class PermissionMethodHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitMethod(node: UMethod) {
+            if (hasPermissionMethodAnnotation(node)) return
+            if (onlyCallsPermissionMethod(node)) {
+                val location = context.getLocation(node.javaPsi.modifierList)
+                val fix = fix()
+                    .annotate(ANNOTATION_PERMISSION_METHOD)
+                    .range(location)
+                    .autoFix()
+                    .build()
+
+                context.report(
+                    ISSUE_CAN_BE_PERMISSION_METHOD,
+                    location,
+                    "Annotate method with @PermissionMethod",
+                    fix
+                )
+            }
+        }
+
+        override fun visitAnnotation(node: UAnnotation) {
+            if (node.qualifiedName != ANNOTATION_PERMISSION_METHOD) return
+            val method = node.getContainingUMethod() ?: return
+
+            if (!isPermissionMethodReturnType(method)) {
+                context.report(
+                    ISSUE_PERMISSION_METHOD_USAGE,
+                    context.getLocation(node),
+                    """
+                            Methods annotated with `@PermissionMethod` should return `void`, \
+                            `boolean`, or `@PackageManager.PermissionResult int`."
+                    """.trimIndent()
+                )
+            }
+
+            if (method.returnType == PsiType.INT &&
+                method.annotations.none { it.hasQualifiedName(ANNOTATION_PERMISSION_RESULT) }
+            ) {
+                context.report(
+                    ISSUE_PERMISSION_METHOD_USAGE,
+                    context.getLocation(node),
+                    """
+                            Methods annotated with `@PermissionMethod` that return `int` should \
+                            also be annotated with `@PackageManager.PermissionResult.`"
+                    """.trimIndent()
+                )
+            }
+        }
+    }
+
+    companion object {
+
+        private val EXPLANATION_PERMISSION_METHOD_USAGE = """
+            `@PermissionMethod` should annotate methods that ONLY perform permission lookups. \
+            Said methods should return `boolean`, `@PackageManager.PermissionResult int`, or return \
+            `void` and potentially throw `SecurityException`.
+        """.trimIndent()
+
+        @JvmField
+        val ISSUE_PERMISSION_METHOD_USAGE = Issue.create(
+            id = "PermissionMethodUsage",
+            briefDescription = "@PermissionMethod used incorrectly",
+            explanation = EXPLANATION_PERMISSION_METHOD_USAGE,
+            category = Category.CORRECTNESS,
+            priority = 5,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                PermissionMethodDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            ),
+            enabledByDefault = true
+        )
+
+        private val EXPLANATION_CAN_BE_PERMISSION_METHOD = """
+            Methods that only call other methods annotated with @PermissionMethod (and do NOTHING else) can themselves \
+            be annotated with @PermissionMethod.  For example:
+            ```
+            void wrapperHelper() {
+              // Context.enforceCallingPermission is annotated with @PermissionMethod
+              context.enforceCallingPermission(SOME_PERMISSION)
+            }
+            ```
+        """.trimIndent()
+
+        @JvmField
+        val ISSUE_CAN_BE_PERMISSION_METHOD = Issue.create(
+            id = "CanBePermissionMethod",
+            briefDescription = "Method can be annotated with @PermissionMethod",
+            explanation = EXPLANATION_CAN_BE_PERMISSION_METHOD,
+            category = Category.SECURITY,
+            priority = 5,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                PermissionMethodDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            ),
+            enabledByDefault = false
+        )
+
+        private fun isPermissionMethodReturnType(method: UMethod): Boolean =
+            listOf(PsiType.VOID, PsiType.INT, PsiType.BOOLEAN).contains(method.returnType)
+
+        /**
+         * Identifies methods that...
+         * DO call other methods annotated with @PermissionMethod
+         * DO NOT do anything else
+         */
+        private fun onlyCallsPermissionMethod(method: UMethod): Boolean {
+            val body = method.uastBody as? UBlockExpression ?: return false
+            if (body.expressions.isEmpty()) return false
+            for (expression in body.expressions) {
+                when (expression) {
+                    is UQualifiedReferenceExpression -> {
+                        if (!isPermissionMethodCall(expression.selector)) return false
+                    }
+                    is UReturnExpression -> {
+                        if (!isPermissionMethodCall(expression.returnExpression)) return false
+                    }
+                    is UCallExpression -> {
+                        if (!isPermissionMethodCall(expression)) return false
+                    }
+                    is UIfExpression -> {
+                        if (expression.thenExpression !is UReturnExpression) return false
+                        if (!isPermissionMethodCall(expression.condition)) return false
+                    }
+                    else -> return false
+                }
+            }
+            return true
+        }
+
+        private fun isPermissionMethodCall(expression: UExpression?): Boolean {
+            return when (expression) {
+                is UQualifiedReferenceExpression ->
+                    return isPermissionMethodCall(expression.selector)
+                is UCallExpression -> {
+                    val calledMethod = expression.resolve()?.getUMethod() ?: return false
+                    return hasPermissionMethodAnnotation(calledMethod)
+                }
+                else -> false
+            }
+        }
+
+        private fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
+                .any { it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD) }
+    }
+}
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
new file mode 100644
index 0000000..06c098d
--- /dev/null
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.parcel
+
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Location
+import com.intellij.psi.PsiArrayType
+import com.intellij.psi.PsiCallExpression
+import com.intellij.psi.PsiClassType
+import com.intellij.psi.PsiIntersectionType
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiType
+import com.intellij.psi.PsiTypeParameter
+import com.intellij.psi.PsiWildcardType
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UVariable
+
+/**
+ * Subclass this class and override {@link #getBoundingClass} to report an unsafe Parcel API issue
+ * with a fix that migrates towards the new safer API by appending an argument in the form of
+ * {@code com.package.ItemType.class} coming from the result of the overridden method.
+ */
+abstract class CallMigrator(
+        val method: Method,
+        private val rejects: Set<String> = emptySet(),
+) {
+    open fun report(context: JavaContext, call: UCallExpression, method: PsiMethod) {
+        val location = context.getLocation(call)
+        val itemType = filter(getBoundingClass(context, call, method))
+        val fix = (itemType as? PsiClassType)?.let { type ->
+            getParcelFix(location, this.method.name, getArgumentSuffix(type))
+        }
+        val message = "Unsafe `${this.method.className}.${this.method.name}()` API usage"
+        context.report(SaferParcelChecker.ISSUE_UNSAFE_API_USAGE, call, location, message, fix)
+    }
+
+    protected open fun getArgumentSuffix(type: PsiClassType) =
+            ", ${type.rawType().canonicalText}.class"
+
+    protected open fun getBoundingClass(
+            context: JavaContext,
+            call: UCallExpression,
+            method: PsiMethod,
+    ): PsiType? = null
+
+    protected fun getItemType(type: PsiType, container: String): PsiClassType? {
+        val supers = getParentTypes(type).mapNotNull { it as? PsiClassType }
+        val containerType = supers.firstOrNull { it.rawType().canonicalText == container }
+                ?: return null
+        val itemType = containerType.parameters.getOrNull(0) ?: return null
+        // TODO: Expand to other types, see PsiTypeVisitor
+        return when (itemType) {
+            is PsiClassType -> itemType
+            is PsiWildcardType -> itemType.bound as PsiClassType
+            else -> null
+        }
+    }
+
+    /**
+     * Tries to obtain the type expected by the "receiving" end given a certain {@link UElement}.
+     *
+     * This could be an assignment, an argument passed to a method call, to a constructor call, a
+     * type cast, etc. If no receiving end is found, the type of the UExpression itself is returned.
+     */
+    protected fun getReceivingType(expression: UElement): PsiType? {
+        val parent = expression.uastParent
+        var type = when (parent) {
+            is UCallExpression -> {
+                val i = parent.valueArguments.indexOf(expression)
+                val psiCall = parent.sourcePsi as? PsiCallExpression ?: return null
+                val typeSubstitutor = psiCall.resolveMethodGenerics().substitutor
+                val method = psiCall.resolveMethod()!!
+                method.getSignature(typeSubstitutor).parameterTypes[i]
+            }
+            is UVariable -> parent.type
+            is UExpression -> parent.getExpressionType()
+            else -> null
+        }
+        if (type == null && expression is UExpression) {
+            type = expression.getExpressionType()
+        }
+        return type
+    }
+
+    protected fun filter(type: PsiType?): PsiType? {
+        // It's important that PsiIntersectionType case is above the one that check the type in
+        // rejects, because for intersect types, the canonicalText is one of the terms.
+        if (type is PsiIntersectionType) {
+            return type.conjuncts.mapNotNull(this::filter).firstOrNull()
+        }
+        if (type == null || type.canonicalText in rejects) {
+            return null
+        }
+        if (type is PsiClassType && type.resolve() is PsiTypeParameter) {
+            return null
+        }
+        return type
+    }
+
+    private fun getParentTypes(type: PsiType): Set<PsiType> =
+            type.superTypes.flatMap(::getParentTypes).toSet() + type
+
+    protected fun getParcelFix(location: Location, method: String, arguments: String) =
+            LintFix
+                    .create()
+                    .name("Migrate to safer Parcel.$method() API")
+                    .replace()
+                    .range(location)
+                    .pattern("$method\\s*\\(((?:.|\\n)*)\\)")
+                    .with("\\k<1>$arguments")
+                    .autoFix()
+                    .build()
+}
+
+/**
+ * This class derives the type to be appended by inferring the generic type of the {@code container}
+ * type (eg. "java.util.List") of the {@code argument}-th argument.
+ */
+class ContainerArgumentMigrator(
+        method: Method,
+        private val argument: Int,
+        private val container: String,
+        rejects: Set<String> = emptySet(),
+) : CallMigrator(method, rejects) {
+    override fun getBoundingClass(
+            context: JavaContext, call: UCallExpression, method: PsiMethod
+    ): PsiType? {
+        val firstParamType = call.valueArguments[argument].getExpressionType() ?: return null
+        return getItemType(firstParamType, container)!!
+    }
+
+    /**
+     * We need to insert a casting construct in the class parameter. For example:
+     *   (Class<Foo<Bar>>) (Class<?>) Foo.class.
+     * This is needed for when the arguments of the conflict (eg. when there is List<Foo<Bar>> and
+     * class type is Class<Foo?).
+     */
+    override fun getArgumentSuffix(type: PsiClassType): String {
+        if (type.parameters.isNotEmpty()) {
+            val rawType = type.rawType()
+            return ", (Class<${type.canonicalText}>) (Class<?>) ${rawType.canonicalText}.class"
+        }
+        return super.getArgumentSuffix(type)
+    }
+}
+
+/**
+ * This class derives the type to be appended by inferring the generic type of the {@code container}
+ * type (eg. "java.util.List") of the return type of the method.
+ */
+class ContainerReturnMigrator(
+        method: Method,
+        private val container: String,
+        rejects: Set<String> = emptySet(),
+) : CallMigrator(method, rejects) {
+    override fun getBoundingClass(
+            context: JavaContext, call: UCallExpression, method: PsiMethod
+    ): PsiType? {
+        val type = getReceivingType(call.uastParent!!) ?: return null
+        return getItemType(type, container)
+    }
+}
+
+/**
+ * This class derives the type to be appended by inferring the expected type for the method result.
+ */
+class ReturnMigrator(
+        method: Method,
+        rejects: Set<String> = emptySet(),
+) : CallMigrator(method, rejects) {
+    override fun getBoundingClass(
+            context: JavaContext, call: UCallExpression, method: PsiMethod
+    ): PsiType? {
+        return getReceivingType(call.uastParent!!)
+    }
+}
+
+/**
+ * This class appends the class loader and the class object by deriving the type from the method
+ * result.
+ */
+class ReturnMigratorWithClassLoader(
+        method: Method,
+        rejects: Set<String> = emptySet(),
+) : CallMigrator(method, rejects) {
+    override fun getBoundingClass(
+            context: JavaContext, call: UCallExpression, method: PsiMethod
+    ): PsiType? {
+        return getReceivingType(call.uastParent!!)
+    }
+
+    override fun getArgumentSuffix(type: PsiClassType): String =
+            "${type.rawType().canonicalText}.class.getClassLoader(), " +
+                    "${type.rawType().canonicalText}.class"
+
+}
+
+/**
+ * This class derives the type to be appended by inferring the expected array type
+ * for the method result.
+ */
+class ArrayReturnMigrator(
+    method: Method,
+    rejects: Set<String> = emptySet(),
+) : CallMigrator(method, rejects) {
+    override fun getBoundingClass(
+           context: JavaContext, call: UCallExpression, method: PsiMethod
+    ): PsiType? {
+        val type = getReceivingType(call.uastParent!!)
+        return (type as? PsiArrayType)?.componentType
+    }
+}
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/Method.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/Method.kt
new file mode 100644
index 0000000..0826e8e
--- /dev/null
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/Method.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.parcel
+
+data class Method(
+    val params: List<String>,
+    val clazz: String,
+    val name: String,
+    val parameters: List<String>
+) {
+    constructor(
+        clazz: String,
+        name: String,
+        parameters: List<String>
+    ) : this(
+            listOf(), clazz, name, parameters
+    )
+
+    val signature: String
+        get() {
+            val prefix = if (params.isEmpty()) "" else "${params.joinToString(", ", "<", ">")} "
+            return "$prefix$clazz.$name(${parameters.joinToString()})"
+        }
+
+    val className: String by lazy {
+        clazz.split(".").last()
+    }
+}
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
new file mode 100644
index 0000000..f928263
--- /dev/null
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.parcel
+
+import com.android.tools.lint.detector.api.*
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiSubstitutor
+import com.intellij.psi.PsiType
+import com.intellij.psi.PsiTypeParameter
+import org.jetbrains.uast.UCallExpression
+import java.util.*
+
+@Suppress("UnstableApiUsage")
+class SaferParcelChecker : Detector(), SourceCodeScanner {
+    override fun getApplicableMethodNames(): List<String> =
+            MIGRATORS
+                    .map(CallMigrator::method)
+                    .map(Method::name)
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (!isAtLeastT(context)) return
+        val signature = getSignature(method)
+        val migrator = MIGRATORS.firstOrNull { it.method.signature == signature } ?: return
+        migrator.report(context, node, method)
+    }
+
+    private fun getSignature(method: PsiMethod): String {
+        val name = UastLintUtils.getQualifiedName(method)
+        val signature = method.getSignature(PsiSubstitutor.EMPTY)
+        val parameters =
+                signature.parameterTypes.joinToString(transform = PsiType::getCanonicalText)
+        val types = signature.typeParameters.map(PsiTypeParameter::getName)
+        val prefix = if (types.isEmpty()) "" else types.joinToString(", ", "<", ">") + " "
+        return "$prefix$name($parameters)"
+    }
+
+    private fun isAtLeastT(context: Context): Boolean {
+        val project = if (context.isGlobalAnalysis()) context.mainProject else context.project
+        return project.isAndroidProject && project.minSdkVersion.featureLevel >= 33
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE_UNSAFE_API_USAGE: Issue = Issue.create(
+                id = "UnsafeParcelApi",
+                briefDescription = "Use of unsafe deserialization API",
+                explanation = """
+                    You are using a deprecated deserialization API that doesn't accept the expected class as\
+                     a parameter. This means that unexpected classes could be instantiated and\
+                     unexpected code executed.
+
+                    Please migrate to the safer alternative that takes an extra Class<T> parameter.
+                    """,
+                category = Category.SECURITY,
+                priority = 8,
+                severity = Severity.WARNING,
+
+                implementation = Implementation(
+                        SaferParcelChecker::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                )
+        )
+
+        // Parcel
+        private val PARCEL_METHOD_READ_SERIALIZABLE = Method("android.os.Parcel", "readSerializable", listOf())
+        private val PARCEL_METHOD_READ_ARRAY_LIST = Method("android.os.Parcel", "readArrayList", listOf("java.lang.ClassLoader"))
+        private val PARCEL_METHOD_READ_LIST = Method("android.os.Parcel", "readList", listOf("java.util.List", "java.lang.ClassLoader"))
+        private val PARCEL_METHOD_READ_PARCELABLE = Method(listOf("T"), "android.os.Parcel", "readParcelable", listOf("java.lang.ClassLoader"))
+        private val PARCEL_METHOD_READ_PARCELABLE_LIST = Method(listOf("T"), "android.os.Parcel", "readParcelableList", listOf("java.util.List<T>", "java.lang.ClassLoader"))
+        private val PARCEL_METHOD_READ_SPARSE_ARRAY = Method(listOf("T"), "android.os.Parcel", "readSparseArray", listOf("java.lang.ClassLoader"))
+        private val PARCEL_METHOD_READ_ARRAY = Method("android.os.Parcel", "readArray", listOf("java.lang.ClassLoader"))
+        private val PARCEL_METHOD_READ_PARCELABLE_ARRAY = Method("android.os.Parcel", "readParcelableArray", listOf("java.lang.ClassLoader"))
+
+        // Bundle
+        private val BUNDLE_METHOD_GET_SERIALIZABLE = Method("android.os.Bundle", "getSerializable", listOf("java.lang.String"))
+        private val BUNDLE_METHOD_GET_PARCELABLE = Method(listOf("T"), "android.os.Bundle", "getParcelable", listOf("java.lang.String"))
+        private val BUNDLE_METHOD_GET_PARCELABLE_ARRAY_LIST = Method(listOf("T"), "android.os.Bundle", "getParcelableArrayList", listOf("java.lang.String"))
+        private val BUNDLE_METHOD_GET_PARCELABLE_ARRAY = Method("android.os.Bundle", "getParcelableArray", listOf("java.lang.String"))
+        private val BUNDLE_METHOD_GET_SPARSE_PARCELABLE_ARRAY = Method(listOf("T"), "android.os.Bundle", "getSparseParcelableArray", listOf("java.lang.String"))
+
+        // Intent
+        private val INTENT_METHOD_GET_SERIALIZABLE_EXTRA = Method("android.content.Intent", "getSerializableExtra", listOf("java.lang.String"))
+        private val INTENT_METHOD_GET_PARCELABLE_EXTRA = Method(listOf("T"), "android.content.Intent", "getParcelableExtra", listOf("java.lang.String"))
+        private val INTENT_METHOD_GET_PARCELABLE_ARRAY_EXTRA = Method("android.content.Intent", "getParcelableArrayExtra", listOf("java.lang.String"))
+        private val INTENT_METHOD_GET_PARCELABLE_ARRAY_LIST_EXTRA = Method(listOf("T"), "android.content.Intent", "getParcelableArrayListExtra", listOf("java.lang.String"))
+
+        // TODO: Write migrators for methods below
+        private val PARCEL_METHOD_READ_PARCELABLE_CREATOR = Method("android.os.Parcel", "readParcelableCreator", listOf("java.lang.ClassLoader"))
+
+        private val MIGRATORS = listOf(
+            ReturnMigrator(PARCEL_METHOD_READ_PARCELABLE, setOf("android.os.Parcelable")),
+            ContainerArgumentMigrator(PARCEL_METHOD_READ_LIST, 0, "java.util.List"),
+            ContainerReturnMigrator(PARCEL_METHOD_READ_ARRAY_LIST, "java.util.Collection"),
+            ContainerReturnMigrator(PARCEL_METHOD_READ_SPARSE_ARRAY, "android.util.SparseArray"),
+            ContainerArgumentMigrator(PARCEL_METHOD_READ_PARCELABLE_LIST, 0, "java.util.List"),
+            ReturnMigratorWithClassLoader(PARCEL_METHOD_READ_SERIALIZABLE),
+            ArrayReturnMigrator(PARCEL_METHOD_READ_ARRAY, setOf("java.lang.Object")),
+            ArrayReturnMigrator(PARCEL_METHOD_READ_PARCELABLE_ARRAY, setOf("android.os.Parcelable")),
+
+            ReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE, setOf("android.os.Parcelable")),
+            ContainerReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE_ARRAY_LIST, "java.util.Collection", setOf("android.os.Parcelable")),
+            ArrayReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE_ARRAY, setOf("android.os.Parcelable")),
+            ContainerReturnMigrator(BUNDLE_METHOD_GET_SPARSE_PARCELABLE_ARRAY, "android.util.SparseArray", setOf("android.os.Parcelable")),
+            ReturnMigrator(BUNDLE_METHOD_GET_SERIALIZABLE, setOf("java.io.Serializable")),
+
+            ReturnMigrator(INTENT_METHOD_GET_PARCELABLE_EXTRA, setOf("android.os.Parcelable")),
+            ContainerReturnMigrator(INTENT_METHOD_GET_PARCELABLE_ARRAY_LIST_EXTRA, "java.util.Collection", setOf("android.os.Parcelable")),
+            ArrayReturnMigrator(INTENT_METHOD_GET_PARCELABLE_ARRAY_EXTRA, setOf("android.os.Parcelable")),
+            ReturnMigrator(INTENT_METHOD_GET_SERIALIZABLE_EXTRA, setOf("java.io.Serializable")),
+        )
+    }
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
similarity index 88%
rename from tools/lint/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
index e1a5c61..d90f3e3 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
+++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
@@ -27,12 +27,13 @@
     override fun getDetector(): Detector = CallingIdentityTokenDetector()
 
     override fun getIssues(): List<Issue> = listOf(
-            CallingIdentityTokenDetector.ISSUE_UNUSED_TOKEN,
-            CallingIdentityTokenDetector.ISSUE_NON_FINAL_TOKEN,
-            CallingIdentityTokenDetector.ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
-            CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
-            CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
-            CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY
+        CallingIdentityTokenDetector.ISSUE_UNUSED_TOKEN,
+        CallingIdentityTokenDetector.ISSUE_NON_FINAL_TOKEN,
+        CallingIdentityTokenDetector.ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
+        CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
+        CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
+        CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY,
+        CallingIdentityTokenDetector.ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE
     )
 
     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
@@ -41,8 +42,8 @@
 
     fun testDoesNotDetectIssuesInCorrectScenario() {
         lint().files(
-                java(
-                    """
+            java(
+                """
                     package test.pkg;
                     import android.os.Binder;
                     public class TestClass1 extends Binder {
@@ -62,22 +63,29 @@
                             } finally {
                                 restoreCallingIdentity(token3);
                             }
+                            final Long token4 = true ? Binder.clearCallingIdentity() : null;
+                            try {
+                            } finally {
+                                if (token4 != null) {
+                                    restoreCallingIdentity(token4);
+                                }
+                            }
                         }
                     }
                    """
-                ).indented(),
-                *stubs
+            ).indented(),
+            *stubs
         )
-                .run()
-                .expectClean()
+            .run()
+            .expectClean()
     }
 
     /** Unused token issue tests */
 
     fun testDetectsUnusedTokens() {
         lint().files(
-                java(
-                    """
+            java(
+                """
                     package test.pkg;
                     import android.os.Binder;
                     public class TestClass1 extends Binder {
@@ -101,12 +109,12 @@
                         }
                     }
                     """
-                ).indented(),
-                *stubs
+            ).indented(),
+            *stubs
         )
-                .run()
-                .expect(
-                        """
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass1.java:5: Warning: token1 has not been used to \
                         restore the calling identity. Introduce a try-finally after the \
                         declaration and call Binder.restoreCallingIdentity(token1) in finally or \
@@ -127,13 +135,13 @@
                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                         0 errors, 3 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
     fun testDetectsUnusedTokensInScopes() {
         lint().files(
-                java(
-                    """
+            java(
+                """
                     package test.pkg;
                     import android.os.Binder;
                     public class TestClass1 {
@@ -152,12 +160,12 @@
                         }
                     }
                     """
-                ).indented(),
-                *stubs
+            ).indented(),
+            *stubs
         )
-                .run()
-                .expect(
-                        """
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass1.java:5: Warning: token has not been used to \
                         restore the calling identity. Introduce a try-finally after the \
                         declaration and call Binder.restoreCallingIdentity(token) in finally or \
@@ -166,13 +174,13 @@
                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                         0 errors, 1 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
     fun testDoesNotDetectUsedTokensInScopes() {
         lint().files(
-                java(
-                    """
+            java(
+                """
                     package test.pkg;
                     import android.os.Binder;
                     public class TestClass1 {
@@ -192,17 +200,17 @@
                         }
                     }
                     """
-                ).indented(),
-                *stubs
+            ).indented(),
+            *stubs
         )
-                .run()
-                .expectClean()
+            .run()
+            .expectClean()
     }
 
     fun testDetectsUnusedTokensWithSimilarNamesInScopes() {
         lint().files(
-                java(
-                    """
+            java(
+                """
                     package test.pkg;
                     import android.os.Binder;
                     public class TestClass1 {
@@ -220,12 +228,12 @@
                         }
                     }
                     """
-                ).indented(),
-                *stubs
+            ).indented(),
+            *stubs
         )
-                .run()
-                .expect(
-                        """
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass1.java:5: Warning: token has not been used to \
                         restore the calling identity. Introduce a try-finally after the \
                         declaration and call Binder.restoreCallingIdentity(token) in finally or \
@@ -240,15 +248,15 @@
                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                         0 errors, 2 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
     /** Non-final token issue tests */
 
     fun testDetectsNonFinalTokens() {
         lint().files(
-                java(
-                    """
+            java(
+                """
                     package test.pkg;
                     import android.os.Binder;
                     public class TestClass1 extends Binder {
@@ -271,12 +279,12 @@
                         }
                     }
                     """
-                ).indented(),
-                *stubs
+            ).indented(),
+            *stubs
         )
-                .run()
-                .expect(
-                        """
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass1.java:5: Warning: token1 is a non-final token from \
                         Binder.clearCallingIdentity(). Add final keyword to token1. \
                         [NonFinalTokenOfOriginalCallingIdentity]
@@ -294,7 +302,7 @@
                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                         0 errors, 3 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
     /** Nested clearCallingIdentity() calls issue tests */
@@ -302,8 +310,8 @@
     fun testDetectsNestedClearCallingIdentityCalls() {
         // Pattern: clear - clear - clear - restore - restore - restore
         lint().files(
-                java(
-                    """
+            java(
+                """
                     package test.pkg;
                     import android.os.Binder;
                     public class TestClass1 extends Binder {
@@ -326,12 +334,12 @@
                         }
                     }
                     """
-                ).indented(),
-                *stubs
+            ).indented(),
+            *stubs
         )
-                .run()
-                .expect(
-                        """
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass1.java:7: Warning: The calling identity has already \
                         been cleared and returned into token1. Move token2 declaration after \
                         restoring the calling identity with Binder.restoreCallingIdentity(token1). \
@@ -348,15 +356,15 @@
                             src/test/pkg/TestClass1.java:5: Location of the token1 declaration.
                         0 errors, 2 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
     /** clearCallingIdentity() not followed by try-finally issue tests */
 
     fun testDetectsClearIdentityCallNotFollowedByTryFinally() {
         lint().files(
-                java(
-                    """
+            java(
+                """
                     package test.pkg;
                     import android.os.Binder;
                     public class TestClass1 extends Binder{
@@ -397,12 +405,12 @@
                         }
                     }
                     """
-                ).indented(),
-                *stubs
+            ).indented(),
+            *stubs
         )
-                .run()
-                .expect(
-                        """
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass1.java:5: Warning: You cleared the calling identity \
                         and returned the result into token, but the next statement is not a \
                         try-finally statement. Define a try-finally block after token declaration \
@@ -445,15 +453,15 @@
                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                         0 errors, 5 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
     /** restoreCallingIdentity() call not in finally block issue tests */
 
     fun testDetectsRestoreCallingIdentityCallNotInFinally() {
         lint().files(
-                java(
-                    """
+            java(
+                """
                     package test.pkg;
                     import android.os.Binder;
                     public class TestClass1 extends Binder {
@@ -482,12 +490,12 @@
                         }
                     }
                     """
-                ).indented(),
-                *stubs
+            ).indented(),
+            *stubs
         )
-                .run()
-                .expect(
-                        """
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass1.java:10: Warning: \
                         Binder.restoreCallingIdentity(token) is not an immediate child of the \
                         finally block of the try statement after token declaration. Surround the c\
@@ -511,13 +519,13 @@
                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                         0 errors, 3 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
     fun testDetectsRestoreCallingIdentityCallNotInFinallyInScopes() {
         lint().files(
-                java(
-                    """
+            java(
+                """
                     package test.pkg;
                     import android.os.Binder;
                     public class TestClass1 extends Binder {
@@ -560,12 +568,12 @@
                         }
                     }
                     """
-                ).indented(),
-                *stubs
+            ).indented(),
+            *stubs
         )
-                .run()
-                .expect(
-                        """
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass1.java:11: Warning: \
                         Binder.restoreCallingIdentity(token1) is not an immediate child of the \
                         finally block of the try statement after token1 declaration. Surround the \
@@ -596,15 +604,15 @@
                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                         0 errors, 4 warnings
                         """.addLineContinuation()
-                )
+            )
     }
 
     /** Use of caller-aware methods after clearCallingIdentity() issue tests */
 
     fun testDetectsUseOfCallerAwareMethodsWithClearedIdentityIssuesInScopes() {
         lint().files(
-                java(
-                    """
+            java(
+                """
                     package test.pkg;
                     import android.os.Binder;
                     import android.os.UserHandle;
@@ -632,12 +640,12 @@
                         }
                     }
                     """
-                ).indented(),
-                *stubs
+            ).indented(),
+            *stubs
         )
-                .run()
-                .expect(
-                        """
+            .run()
+            .expect(
+                """
                         src/test/pkg/TestClass1.java:8: Warning: You cleared the original identity \
                         with Binder.clearCallingIdentity() and returned into token, so \
                         getCallingPid() will be using your own identity instead of the \
@@ -736,13 +744,58 @@
                                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                         0 errors, 12 warnings
                         """.addLineContinuation()
-                )
+            )
+    }
+
+    /** Result of Binder.clearCallingIdentity() is not stored in a variable issue tests */
+
+    fun testDetectsResultOfClearIdentityCallNotStoredInVariable() {
+        lint().files(
+            java(
+                """
+                    package test.pkg;
+                    import android.os.Binder;
+                    public class TestClass1 extends Binder {
+                        private void testMethod() {
+                            Binder.clearCallingIdentity();
+                            android.os.Binder.clearCallingIdentity();
+                            clearCallingIdentity();
+                        }
+                    }
+                    """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                        src/test/pkg/TestClass1.java:5: Warning: You cleared the original identity \
+                        with Binder.clearCallingIdentity() but did not store the result in a \
+                        variable. You need to store the result in a variable and restore it later. \
+                        [ResultOfClearIdentityCallNotStoredInVariable]
+                                Binder.clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:6: Warning: You cleared the original identity \
+                        with android.os.Binder.clearCallingIdentity() but did not store the result \
+                        in a variable. You need to store the result in a variable and restore it \
+                        later. [ResultOfClearIdentityCallNotStoredInVariable]
+                                android.os.Binder.clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:7: Warning: You cleared the original identity \
+                        with clearCallingIdentity() but did not store the result in a variable. \
+                        You need to store the result in a variable and restore it later. \
+                        [ResultOfClearIdentityCallNotStoredInVariable]
+                                clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 3 warnings
+                        """.addLineContinuation()
+            )
     }
 
     /** Stubs for classes used for testing */
 
     private val binderStub: TestFile = java(
-            """
+        """
             package android.os;
             public class Binder {
                 public static final native long clearCallingIdentity() {
@@ -767,7 +820,7 @@
     ).indented()
 
     private val userHandleStub: TestFile = java(
-            """
+        """
             package android.os;
             import android.annotation.AppIdInt;
             import android.annotation.UserIdInt;
@@ -792,7 +845,7 @@
     ).indented()
 
     private val userIdIntStub: TestFile = java(
-            """
+        """
             package android.annotation;
             public @interface UserIdInt {
             }
@@ -800,7 +853,7 @@
     ).indented()
 
     private val appIdIntStub: TestFile = java(
-            """
+        """
             package android.annotation;
             public @interface AppIdInt {
             }
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt
new file mode 100644
index 0000000..a70644a
--- /dev/null
+++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class PackageVisibilityDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = PackageVisibilityDetector()
+
+    override fun getIssues(): MutableList<Issue> = mutableListOf(
+        PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    fun testDetectIssuesParameterDoesNotApplyPackageVisibilityFilters() {
+        lint().files(java(
+            """
+            package com.android.server.lint.test;
+            import android.internal.test.IFoo;
+
+            public class TestClass extends IFoo.Stub {
+                @Override
+                public boolean hasPackage(String packageName) {
+                    return packageName != null;
+                }
+            }
+            """).indented(), *stubs
+        ).run().expect(
+                """
+                src/com/android/server/lint/test/TestClass.java:6: Warning: \
+                Api: hasPackage contains a package name parameter: packageName does not apply \
+                package visibility filtering rules. \
+                [ApiMightLeakAppVisibility]
+                    public boolean hasPackage(String packageName) {
+                                              ~~~~~~~~~~~~~~~~~~
+                0 errors, 1 warnings
+                """.addLineContinuation()
+        )
+    }
+
+    fun testDoesNotDetectIssuesApiInvokesAppOps() {
+        lint().files(java(
+            """
+            package com.android.server.lint.test;
+            import android.app.AppOpsManager;
+            import android.os.Binder;
+            import android.internal.test.IFoo;
+
+            public class TestClass extends IFoo.Stub {
+                private AppOpsManager mAppOpsManager;
+
+                @Override
+                public boolean hasPackage(String packageName) {
+                    checkPackage(packageName);
+                    return packageName != null;
+                }
+
+                private void checkPackage(String packageName) {
+                    mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
+                }
+            }
+            """
+        ).indented(), *stubs).run().expectClean()
+    }
+
+    fun testDoesNotDetectIssuesApiInvokesEnforcePermission() {
+        lint().files(java(
+            """
+            package com.android.server.lint.test;
+            import android.content.Context;
+            import android.internal.test.IFoo;
+
+            public class TestClass extends IFoo.Stub {
+                private Context mContext;
+
+                @Override
+                public boolean hasPackage(String packageName) {
+                    enforcePermission();
+                    return packageName != null;
+                }
+
+                private void enforcePermission() {
+                    mContext.checkCallingPermission(
+                            android.Manifest.permission.ACCESS_INPUT_FLINGER);
+                }
+            }
+            """
+        ).indented(), *stubs).run().expectClean()
+    }
+
+    fun testDoesNotDetectIssuesApiInvokesPackageManager() {
+        lint().files(java(
+            """
+            package com.android.server.lint.test;
+            import android.content.pm.PackageInfo;
+            import android.content.pm.PackageManager;
+            import android.internal.test.IFoo;
+
+            public class TestClass extends IFoo.Stub {
+                private PackageManager mPackageManager;
+
+                @Override
+                public boolean hasPackage(String packageName) {
+                    return getPackageInfo(packageName) != null;
+                }
+
+                private PackageInfo getPackageInfo(String packageName) {
+                    try {
+                        return mPackageManager.getPackageInfo(packageName, 0);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        return null;
+                    }
+                }
+            }
+            """
+        ).indented(), *stubs).run().expectClean()
+    }
+
+    fun testDetectIssuesApiInvokesPackageManagerAndClearCallingIdentify() {
+        lint().files(java(
+            """
+            package com.android.server.lint.test;
+            import android.content.pm.PackageInfo;
+            import android.content.pm.PackageManager;
+            import android.internal.test.IFoo;import android.os.Binder;
+
+            public class TestClass extends IFoo.Stub {
+                private PackageManager mPackageManager;
+
+                @Override
+                public boolean hasPackage(String packageName) {
+                    return getPackageInfo(packageName) != null;
+                }
+
+                private PackageInfo getPackageInfo(String packageName) {
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        try {
+                            return mPackageManager.getPackageInfo(packageName, 0);
+                        } catch (PackageManager.NameNotFoundException e) {
+                            return null;
+                        }
+                    } finally{
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            }
+            """).indented(), *stubs
+        ).run().expect(
+                """
+                src/com/android/server/lint/test/TestClass.java:10: Warning: \
+                Api: hasPackage contains a package name parameter: packageName does not apply \
+                package visibility filtering rules. \
+                [ApiMightLeakAppVisibility]
+                    public boolean hasPackage(String packageName) {
+                                              ~~~~~~~~~~~~~~~~~~
+                0 errors, 1 warnings
+                """.addLineContinuation()
+        )
+    }
+
+    fun testDoesNotDetectIssuesApiNotSystemPackagePrefix() {
+        lint().files(java(
+            """
+            package com.test.not.system.prefix;
+            import android.internal.test.IFoo;
+
+            public class TestClass extends IFoo.Stub {
+                @Override
+                public boolean hasPackage(String packageName) {
+                    return packageName != null;
+                }
+            }
+            """
+        ).indented(), *stubs).run().expectClean()
+    }
+
+    private val contextStub: TestFile = java(
+        """
+        package android.content;
+
+        public abstract class Context {
+            public abstract int checkCallingPermission(String permission);
+        }
+        """
+    ).indented()
+
+    private val appOpsManagerStub: TestFile = java(
+        """
+        package android.app;
+
+        public class AppOpsManager {
+            public void checkPackage(int uid, String packageName) {
+            }
+        }
+        """
+    ).indented()
+
+    private val packageManagerStub: TestFile = java(
+        """
+        package android.content.pm;
+        import android.content.pm.PackageInfo;
+
+        public abstract class PackageManager {
+            public static class NameNotFoundException extends AndroidException {
+            }
+
+            public abstract PackageInfo getPackageInfo(String packageName, int flags)
+                    throws NameNotFoundException;
+        }
+        """
+    ).indented()
+
+    private val packageInfoStub: TestFile = java(
+        """
+        package android.content.pm;
+        public class PackageInfo {}
+        """
+    ).indented()
+
+    private val binderStub: TestFile = java(
+        """
+        package android.os;
+
+        public class Binder {
+            public static final native long clearCallingIdentity();
+            public static final native void restoreCallingIdentity(long token);
+            public static final native int getCallingUid();
+        }
+        """
+    ).indented()
+
+    private val interfaceIFooStub: TestFile = java(
+        """
+        package android.internal.test;
+        import android.os.Binder;
+
+        public interface IFoo {
+            boolean hasPackage(String packageName);
+            public abstract static class Stub extends Binder implements IFoo {
+            }
+        }
+        """
+    ).indented()
+
+    private val stubs = arrayOf(contextStub, appOpsManagerStub, packageManagerStub,
+        packageInfoStub, binderStub, interfaceIFooStub)
+
+    // Substitutes "backslash + new line" with an empty string to imitate line continuation
+    private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")
+}
diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
new file mode 100644
index 0000000..e686695
--- /dev/null
+++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
@@ -0,0 +1,823 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.parcel
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class SaferParcelCheckerTest : LintDetectorTest() {
+    override fun getDetector(): Detector = SaferParcelChecker()
+
+    override fun getIssues(): List<Issue> = listOf(
+        SaferParcelChecker.ISSUE_UNSAFE_API_USAGE
+    )
+
+    override fun lint(): TestLintTask =
+        super.lint()
+            .allowMissingSdk(true)
+            // We don't do partial analysis in the platform
+            .skipTestModes(TestMode.PARTIAL)
+
+    /** Parcel Tests */
+
+    fun testParcelDetectUnsafeReadSerializable() {
+        lint()
+            .files(
+                java(
+                    """
+                        package test.pkg;
+                        import android.os.Parcel;
+                        import java.io.Serializable;
+
+                        public class TestClass {
+                            private TestClass(Parcel p) {
+                                Serializable ans = p.readSerializable();
+                            }
+                        }
+                        """
+                ).indented(),
+                *includes
+            )
+            .expectIdenticalTestModeOutput(false)
+            .run()
+            .expect(
+                """
+                        src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readSerializable() \
+                        API usage [UnsafeParcelApi]
+                                Serializable ans = p.readSerializable();
+                                                   ~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testParcelDoesNotDetectSafeReadSerializable() {
+        lint()
+            .files(
+                java(
+                    """
+                        package test.pkg;
+                        import android.os.Parcel;
+                        import java.io.Serializable;
+
+                        public class TestClass {
+                            private TestClass(Parcel p) {
+                                String ans = p.readSerializable(null, String.class);
+                            }
+                        }
+                        """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testParcelDetectUnsafeReadArrayList() {
+        lint()
+            .files(
+                java(
+                    """
+                        package test.pkg;
+                        import android.os.Parcel;
+
+                        public class TestClass {
+                            private TestClass(Parcel p) {
+                                ArrayList ans = p.readArrayList(null);
+                            }
+                        }
+                        """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                        src/test/pkg/TestClass.java:6: Warning: Unsafe Parcel.readArrayList() API \
+                        usage [UnsafeParcelApi]
+                                ArrayList ans = p.readArrayList(null);
+                                                ~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testParcelDoesNotDetectSafeReadArrayList() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        ArrayList<Intent> ans = p.readArrayList(null, Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testParcelDetectUnsafeReadList() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+                                import java.util.List;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        List<Intent> list = new ArrayList<Intent>();
+                                        p.readList(list, null);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                        src/test/pkg/TestClass.java:9: Warning: Unsafe Parcel.readList() API usage \
+                        [UnsafeParcelApi]
+                                p.readList(list, null);
+                                ~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testDParceloesNotDetectSafeReadList() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+                                import java.util.List;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        List<Intent> list = new ArrayList<Intent>();
+                                        p.readList(list, null, Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testParcelDetectUnsafeReadParcelable() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        Intent ans = p.readParcelable(null);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                        src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readParcelable() API \
+                        usage [UnsafeParcelApi]
+                                Intent ans = p.readParcelable(null);
+                                             ~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testParcelDoesNotDetectSafeReadParcelable() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        Intent ans = p.readParcelable(null, Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testParcelDetectUnsafeReadParcelableList() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+                                import java.util.List;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        List<Intent> list = new ArrayList<Intent>();
+                                        List<Intent> ans = p.readParcelableList(list, null);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                        src/test/pkg/TestClass.java:9: Warning: Unsafe Parcel.readParcelableList() \
+                        API usage [UnsafeParcelApi]
+                                List<Intent> ans = p.readParcelableList(list, null);
+                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testParcelDoesNotDetectSafeReadParcelableList() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+                                import java.util.List;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        List<Intent> list = new ArrayList<Intent>();
+                                        List<Intent> ans =
+                                                p.readParcelableList(list, null, Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testParcelDetectUnsafeReadSparseArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+                                import android.util.SparseArray;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        SparseArray<Intent> ans = p.readSparseArray(null);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                        src/test/pkg/TestClass.java:8: Warning: Unsafe Parcel.readSparseArray() API\
+                         usage [UnsafeParcelApi]
+                                SparseArray<Intent> ans = p.readSparseArray(null);
+                                                          ~~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testParcelDoesNotDetectSafeReadSparseArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+                                import android.util.SparseArray;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        SparseArray<Intent> ans =
+                                                p.readSparseArray(null, Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testParcelDetectUnsafeReadSArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        Intent[] ans = p.readArray(null);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                        src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readArray() API\
+                         usage [UnsafeParcelApi]
+                                Intent[] ans = p.readArray(null);
+                                               ~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testParcelDoesNotDetectSafeReadArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        Intent[] ans = p.readArray(null, Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testParcelDetectUnsafeReadParcelableSArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        Intent[] ans = p.readParcelableArray(null);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                        src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readParcelableArray() API\
+                         usage [UnsafeParcelApi]
+                                Intent[] ans = p.readParcelableArray(null);
+                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testParcelDoesNotDetectSafeReadParcelableArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        Intent[] ans = p.readParcelableArray(null, Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    /** Bundle Tests */
+
+    fun testBundleDetectUnsafeGetParcelable() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        Intent ans = b.getParcelable("key");
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                    src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelable() API usage [UnsafeParcelApi]
+                            Intent ans = b.getParcelable("key");
+                                         ~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testBundleDoesNotDetectSafeGetParcelable() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        Intent ans = b.getParcelable("key", Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testBundleDetectUnsafeGetParcelableArrayList() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        ArrayList<Intent> ans = b.getParcelableArrayList("key");
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                    src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelableArrayList() API usage [UnsafeParcelApi]
+                            ArrayList<Intent> ans = b.getParcelableArrayList("key");
+                                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testBundleDoesNotDetectSafeGetParcelableArrayList() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        ArrayList<Intent> ans = b.getParcelableArrayList("key", Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testBundleDetectUnsafeGetParcelableArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        Intent[] ans = b.getParcelableArray("key");
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                    src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelableArray() API usage [UnsafeParcelApi]
+                            Intent[] ans = b.getParcelableArray("key");
+                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testBundleDoesNotDetectSafeGetParcelableArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        Intent[] ans = b.getParcelableArray("key", Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    fun testBundleDetectUnsafeGetSparseParcelableArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        SparseArray<Intent> ans = b.getSparseParcelableArray("key");
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                    src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getSparseParcelableArray() API usage [UnsafeParcelApi]
+                            SparseArray<Intent> ans = b.getSparseParcelableArray("key");
+                                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testBundleDoesNotDetectSafeGetSparseParcelableArray() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Bundle;
+
+                                public class TestClass {
+                                    private TestClass(Bundle b) {
+                                        SparseArray<Intent> ans = b.getSparseParcelableArray("key", Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+    /** Intent Tests */
+
+    fun testIntentDetectUnsafeGetParcelableExtra() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+
+                                public class TestClass {
+                                    private TestClass(Intent i) {
+                                        Intent ans = i.getParcelableExtra("name");
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect(
+                """
+                    src/test/pkg/TestClass.java:6: Warning: Unsafe Intent.getParcelableExtra() API usage [UnsafeParcelApi]
+                            Intent ans = i.getParcelableExtra("name");
+                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                        """.addLineContinuation()
+            )
+    }
+
+    fun testIntentDoesNotDetectSafeGetParcelableExtra() {
+        lint()
+            .files(
+                java(
+                    """
+                                package test.pkg;
+                                import android.content.Intent;
+
+                                public class TestClass {
+                                    private TestClass(Intent i) {
+                                        Intent ans = i.getParcelableExtra("name", Intent.class);
+                                    }
+                                }
+                                """
+                ).indented(),
+                *includes
+            )
+            .run()
+            .expect("No warnings.")
+    }
+
+
+    /** Stubs for classes used for testing */
+
+
+    private val includes =
+        arrayOf(
+            manifest().minSdk("33"),
+            java(
+                """
+                        package android.os;
+                        import java.util.ArrayList;
+                        import java.util.List;
+                        import java.util.Map;
+                        import java.util.HashMap;
+
+                        public final class Parcel {
+                            // Deprecated
+                            public Object[] readArray(ClassLoader loader) { return null; }
+                            public ArrayList readArrayList(ClassLoader loader) { return null; }
+                            public HashMap readHashMap(ClassLoader loader) { return null; }
+                            public void readList(List outVal, ClassLoader loader) {}
+                            public void readMap(Map outVal, ClassLoader loader) {}
+                            public <T extends Parcelable> T readParcelable(ClassLoader loader) { return null; }
+                            public Parcelable[] readParcelableArray(ClassLoader loader) { return null; }
+                            public Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) { return null; }
+                            public <T extends Parcelable> List<T> readParcelableList(List<T> list, ClassLoader cl) { return null; }
+                            public Serializable readSerializable() { return null; }
+                            public <T> SparseArray<T> readSparseArray(ClassLoader loader) { return null; }
+
+                            // Replacements
+                            public <T> T[] readArray(ClassLoader loader, Class<T> clazz) { return null; }
+                            public <T> ArrayList<T> readArrayList(ClassLoader loader, Class<? extends T> clazz) { return null; }
+                            public <K, V> HashMap<K,V> readHashMap(ClassLoader loader, Class<? extends K> clazzKey, Class<? extends V> clazzValue) { return null; }
+                            public <T> void readList(List<? super T> outVal, ClassLoader loader, Class<T> clazz) {}
+                            public <K, V> void readMap(Map<? super K, ? super V> outVal, ClassLoader loader, Class<K> clazzKey, Class<V> clazzValue) {}
+                            public <T> T readParcelable(ClassLoader loader, Class<T> clazz) { return null; }
+                            public <T> T[] readParcelableArray(ClassLoader loader, Class<T> clazz) { return null; }
+                            public <T> Parcelable.Creator<T> readParcelableCreator(ClassLoader loader, Class<T> clazz) { return null; }
+                            public <T> List<T> readParcelableList(List<T> list, ClassLoader cl, Class<T> clazz) { return null; }
+                            public <T> T readSerializable(ClassLoader loader, Class<T> clazz) { return null; }
+                            public <T> SparseArray<T> readSparseArray(ClassLoader loader, Class<? extends T> clazz) { return null; }
+                        }
+                        """
+            ).indented(),
+            java(
+                """
+                        package android.os;
+                        import java.util.ArrayList;
+                        import java.util.List;
+                        import java.util.Map;
+                        import java.util.HashMap;
+
+                        public final class Bundle {
+                            // Deprecated
+                            public <T extends Parcelable> T getParcelable(String key) { return  null; }
+                            public <T extends Parcelable> ArrayList<T> getParcelableArrayList(String key) { return null; }
+                            public Parcelable[] getParcelableArray(String key) { return null; }
+                            public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(String key) { return null; }
+
+                            // Replacements
+                            public <T> T getParcelable(String key, Class<T> clazz) { return  null; }
+                            public <T> ArrayList<T> getParcelableArrayList(String key, Class<? extends T> clazz) { return null; }
+                            public <T> T[] getParcelableArray(String key, Class<T> clazz) { return null; }
+                            public <T> SparseArray<T> getSparseParcelableArray(String key, Class<? extends T> clazz) { return null; }
+
+                        }
+                        """
+            ).indented(),
+            java(
+                """
+                        package android.os;
+                        public interface Parcelable {}
+                        """
+            ).indented(),
+            java(
+                """
+                        package android.content;
+                        public class Intent implements Parcelable, Cloneable {
+                            // Deprecated
+                            public <T extends Parcelable> T getParcelableExtra(String name) { return null; }
+
+                            // Replacements
+                            public <T> T getParcelableExtra(String name, Class<T> clazz) { return null; }
+
+                        }
+                        """
+            ).indented(),
+            java(
+                """
+                        package android.util;
+                        public class SparseArray<E> implements Cloneable {}
+                        """
+            ).indented(),
+        )
+
+    // Substitutes "backslash + new line" with an empty string to imitate line continuation
+    private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")
+}
diff --git a/tools/lint/Android.bp b/tools/lint/global/Android.bp
similarity index 61%
copy from tools/lint/Android.bp
copy to tools/lint/global/Android.bp
index 2601041..bedb7bd 100644
--- a/tools/lint/Android.bp
+++ b/tools/lint/global/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 The Android Open Source Project
+// Copyright (C) 2022 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.
@@ -22,32 +22,44 @@
 }
 
 java_library_host {
-    name: "AndroidFrameworkLintChecker",
+    name: "AndroidGlobalLintChecker",
     srcs: ["checks/src/main/java/**/*.kt"],
     plugins: ["auto_service_plugin"],
     libs: [
         "auto_service_annotations",
         "lint_api",
     ],
+    static_libs: ["AndroidCommonLint"],
     kotlincflags: ["-Xjvm-default=all"],
+    dist: {
+        targets: ["droid"],
+    },
 }
 
 java_test_host {
-    name: "AndroidFrameworkLintCheckerTest",
-    // TODO(b/239881504): Since this test was written, Android
-    // Lint was updated, and now includes classes that were
-    // compiled for java 15. The soong build doesn't support
-    // java 15 yet, so we can't compile against "lint". Disable
-    // the test until java 15 is supported.
-    enabled: false,
+    name: "AndroidGlobalLintCheckerTest",
     srcs: ["checks/src/test/java/**/*.kt"],
     static_libs: [
-        "AndroidFrameworkLintChecker",
+        "AndroidGlobalLintChecker",
         "junit",
         "lint",
         "lint_tests",
     ],
     test_options: {
         unit_test: true,
+        tradefed_options: [
+            {
+                // lint bundles in some classes that were built with older versions
+                // of libraries, and no longer load. Since tradefed tries to load
+                // all classes in the jar to look for tests, it crashes loading them.
+                // Exclude these classes from tradefed's search.
+                name: "exclude-paths",
+                value: "org/apache",
+            },
+            {
+                name: "exclude-paths",
+                value: "META-INF",
+            },
+        ],
     },
 }
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
similarity index 63%
rename from tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
rename to tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
index a6fd9bb..a20266a 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -19,21 +19,19 @@
 import com.android.tools.lint.client.api.IssueRegistry
 import com.android.tools.lint.client.api.Vendor
 import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.android.lint.aidl.EnforcePermissionDetector
+import com.google.android.lint.aidl.EnforcePermissionHelperDetector
+import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
 import com.google.auto.service.AutoService
 
 @AutoService(IssueRegistry::class)
 @Suppress("UnstableApiUsage")
-class AndroidFrameworkIssueRegistry : IssueRegistry() {
+class AndroidGlobalIssueRegistry : IssueRegistry() {
     override val issues = listOf(
-            CallingIdentityTokenDetector.ISSUE_UNUSED_TOKEN,
-            CallingIdentityTokenDetector.ISSUE_NON_FINAL_TOKEN,
-            CallingIdentityTokenDetector.ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
-            CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
-            CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
-            CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY,
-            CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
             EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
-            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION
+            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
+            EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+            SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT,
     )
 
     override val api: Int
@@ -45,6 +43,6 @@
     override val vendor: Vendor = Vendor(
             vendorName = "Android",
             feedbackUrl = "http://b/issues/new?component=315013",
-            contact = "brufino@google.com"
+            contact = "repsonsible-apis@google.com"
     )
-}
+}
\ No newline at end of file
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt
new file mode 100644
index 0000000..ab6d871
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.aidl
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+/**
+ * Abstract class for detectors that look for methods implementing
+ * generated AIDL interface stubs
+ */
+abstract class AidlImplementationDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+            listOf(UMethod::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitMethod(node: UMethod) {
+            val interfaceName = getContainingAidlInterface(context, node)
+                    .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return
+            val body = (node.uastBody as? UBlockExpression) ?: return
+            visitAidlMethod(context, node, interfaceName, body)
+        }
+    }
+
+    abstract fun visitAidlMethod(
+            context: JavaContext,
+            node: UMethod,
+            interfaceName: String,
+            body: UBlockExpression,
+    )
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
new file mode 100644
index 0000000..dcfbe95
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.aidl
+
+const val ANNOTATION_ENFORCE_PERMISSION = "android.annotation.EnforcePermission"
+const val ANNOTATION_REQUIRES_NO_PERMISSION = "android.annotation.RequiresNoPermission"
+const val ANNOTATION_PERMISSION_MANUALLY_ENFORCED = "android.annotation.PermissionManuallyEnforced"
+
+val AIDL_PERMISSION_ANNOTATIONS = listOf(
+        ANNOTATION_ENFORCE_PERMISSION,
+        ANNOTATION_REQUIRES_NO_PERMISSION,
+        ANNOTATION_PERMISSION_MANUALLY_ENFORCED
+)
+
+const val BINDER_CLASS = "android.os.Binder"
+const val IINTERFACE_INTERFACE = "android.os.IInterface"
+
+const val AIDL_PERMISSION_HELPER_SUFFIX = "_enforcePermission"
+
+/**
+ * If a non java (e.g. c++) backend is enabled, the @EnforcePermission
+ * annotation cannot be used.  At time of writing, the mechanism
+ * is not implemented for non java backends.
+ * TODO: b/242564874 (have lint know which interfaces have the c++ backend enabled)
+ * rather than hard coding this list?
+ */
+val EXCLUDED_CPP_INTERFACES = listOf(
+        "AdbTransportType",
+        "FingerprintAndPairDevice",
+        "IAdbCallback",
+        "IAdbManager",
+        "PairDevice",
+        "IStatsBootstrapAtomService",
+        "StatsBootstrapAtom",
+        "StatsBootstrapAtomValue",
+        "FixedSizeArrayExample",
+        "PlaybackTrackMetadata",
+        "RecordTrackMetadata",
+        "SinkMetadata",
+        "SourceMetadata",
+        "IUpdateEngineStable",
+        "IUpdateEngineStableCallback",
+        "AudioCapabilities",
+        "ConfidenceLevel",
+        "ModelParameter",
+        "ModelParameterRange",
+        "Phrase",
+        "PhraseRecognitionEvent",
+        "PhraseRecognitionExtra",
+        "PhraseSoundModel",
+        "Properties",
+        "RecognitionConfig",
+        "RecognitionEvent",
+        "RecognitionMode",
+        "RecognitionStatus",
+        "SoundModel",
+        "SoundModelType",
+        "Status",
+        "IThermalService",
+        "IPowerManager",
+        "ITunerResourceManager"
+)
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
new file mode 100644
index 0000000..0baac2c
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.aidl
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.AnnotationInfo
+import com.android.tools.lint.detector.api.AnnotationOrigin
+import com.android.tools.lint.detector.api.AnnotationUsageInfo
+import com.android.tools.lint.detector.api.AnnotationUsageType
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.ConstantEvaluator
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiArrayInitializerMemberValue
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.toUElement
+
+/**
+ * Lint Detector that ensures that any method overriding a method annotated
+ * with @EnforcePermission is also annotated with the exact same annotation.
+ * The intent is to surface the effective permission checks to the service
+ * implementations.
+ *
+ * This is done with 2 mechanisms:
+ *  1. Visit any annotation usage, to ensure that any derived class will have
+ *     the correct annotation on each methods. This is for the top to bottom
+ *     propagation.
+ *  2. Visit any annotation, to ensure that if a method is annotated, it has
+ *     its ancestor also annotated. This is to avoid having an annotation on a
+ *     Java method without the corresponding annotation on the AIDL interface.
+ */
+class EnforcePermissionDetector : Detector(), SourceCodeScanner {
+
+    override fun applicableAnnotations(): List<String> {
+        return listOf(ANNOTATION_ENFORCE_PERMISSION)
+    }
+
+    override fun getApplicableUastTypes(): List<Class<out UElement>> {
+        return listOf(UAnnotation::class.java)
+    }
+
+    private fun annotationValueGetChildren(elem: PsiElement): Array<PsiElement> {
+        if (elem is PsiArrayInitializerMemberValue)
+            return elem.getInitializers().map { it as PsiElement }.toTypedArray()
+        return elem.getChildren()
+    }
+
+    private fun areAnnotationsEquivalent(
+        context: JavaContext,
+        anno1: PsiAnnotation,
+        anno2: PsiAnnotation
+    ): Boolean {
+        if (anno1.qualifiedName != anno2.qualifiedName) {
+            return false
+        }
+        val attr1 = anno1.parameterList.attributes
+        val attr2 = anno2.parameterList.attributes
+        if (attr1.size != attr2.size) {
+            return false
+        }
+        for (i in attr1.indices) {
+            if (attr1[i].name != attr2[i].name) {
+                return false
+            }
+            val value1 = attr1[i].value ?: return false
+            val value2 = attr2[i].value ?: return false
+            // Try to compare values directly with each other.
+            val v1 = ConstantEvaluator.evaluate(context, value1)
+            val v2 = ConstantEvaluator.evaluate(context, value2)
+            if (v1 != null && v2 != null) {
+                if (v1 != v2) {
+                    return false
+                }
+            } else {
+                val children1 = annotationValueGetChildren(value1)
+                val children2 = annotationValueGetChildren(value2)
+                if (children1.size != children2.size) {
+                    return false
+                }
+                for (j in children1.indices) {
+                    val c1 = ConstantEvaluator.evaluate(context, children1[j])
+                    val c2 = ConstantEvaluator.evaluate(context, children2[j])
+                    if (c1 != c2) {
+                        return false
+                    }
+                }
+            }
+        }
+        return true
+    }
+
+    private fun compareMethods(
+        context: JavaContext,
+        element: UElement,
+        overridingMethod: PsiMethod,
+        overriddenMethod: PsiMethod,
+        checkEquivalence: Boolean = true
+    ) {
+        // If method is not from a Stub subclass, this method shouldn't use @EP at all.
+        // This is handled by EnforcePermissionHelperDetector.
+        if (!isContainedInSubclassOfStub(context, overridingMethod.toUElement() as? UMethod)) {
+            return
+        }
+        val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
+        val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
+        val location = context.getLocation(element)
+        val overridingClass = overridingMethod.parent as PsiClass
+        val overriddenClass = overriddenMethod.parent as PsiClass
+        val overridingName = "${overridingClass.name}.${overridingMethod.name}"
+        val overriddenName = "${overriddenClass.name}.${overriddenMethod.name}"
+        if (overridingAnnotation == null) {
+            val msg = "The method $overridingName overrides the method $overriddenName which " +
+                "is annotated with @EnforcePermission. The same annotation must be used " +
+                "on $overridingName"
+            context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg)
+        } else if (overriddenAnnotation == null) {
+            val msg = "The method $overridingName overrides the method $overriddenName which " +
+                "is not annotated with @EnforcePermission. The same annotation must be " +
+                "used on $overriddenName. Did you forget to annotate the AIDL definition?"
+            context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg)
+        } else if (checkEquivalence && !areAnnotationsEquivalent(
+                    context, overridingAnnotation, overriddenAnnotation)) {
+            val msg = "The method $overridingName is annotated with " +
+                "${overridingAnnotation.text} which differs from the overridden " +
+                "method $overriddenName: ${overriddenAnnotation.text}. The same " +
+                "annotation must be used for both methods."
+            context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg)
+        }
+    }
+
+    override fun visitAnnotationUsage(
+        context: JavaContext,
+        element: UElement,
+        annotationInfo: AnnotationInfo,
+        usageInfo: AnnotationUsageInfo
+    ) {
+        if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE &&
+            annotationInfo.origin == AnnotationOrigin.METHOD) {
+            val overridingMethod = element.sourcePsi as PsiMethod
+            val overriddenMethod = usageInfo.referenced as PsiMethod
+            compareMethods(context, element, overridingMethod, overriddenMethod)
+        }
+    }
+
+    override fun createUastHandler(context: JavaContext): UElementHandler {
+        return object : UElementHandler() {
+            override fun visitAnnotation(node: UAnnotation) {
+                if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) {
+                    return
+                }
+                val method = node.uastParent as? UMethod ?: return
+                val overridingMethod = method as PsiMethod
+                val parents = overridingMethod.findSuperMethods()
+                for (overriddenMethod in parents) {
+                    // The equivalence check can be skipped, if both methods are
+                    // annotated, it will be verified by visitAnnotationUsage.
+                    compareMethods(context, method, overridingMethod,
+                        overriddenMethod, checkEquivalence = false)
+                }
+            }
+        }
+    }
+
+    companion object {
+        val EXPLANATION = """
+            The @EnforcePermission annotation is used to indicate that the underlying binder code
+            has already verified the caller's permissions before calling the appropriate method. The
+            verification code is usually generated by the AIDL compiler, which also takes care of
+            annotating the generated Java code.
+
+            In order to surface that information to platform developers, the same annotation must be
+            used on the implementation class or methods.
+            """
+
+        val ISSUE_MISSING_ENFORCE_PERMISSION: Issue = Issue.create(
+            id = "MissingEnforcePermissionAnnotation",
+            briefDescription = "Missing @EnforcePermission annotation on Binder method",
+            explanation = EXPLANATION,
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                    EnforcePermissionDetector::class.java,
+                    Scope.JAVA_FILE_SCOPE
+            )
+        )
+
+        val ISSUE_MISMATCHING_ENFORCE_PERMISSION: Issue = Issue.create(
+            id = "MismatchingEnforcePermissionAnnotation",
+            briefDescription = "Incorrect @EnforcePermission annotation on Binder method",
+            explanation = EXPLANATION,
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                    EnforcePermissionDetector::class.java,
+                    Scope.JAVA_FILE_SCOPE
+            )
+        )
+    }
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
new file mode 100644
index 0000000..25d208d
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.UastLintUtils.Companion.getAnnotationBooleanValue
+import com.android.tools.lint.detector.api.UastLintUtils.Companion.getAnnotationStringValues
+import com.android.tools.lint.detector.api.findSelector
+import com.android.tools.lint.detector.api.getUMethod
+import com.google.android.lint.findCallExpression
+import com.google.android.lint.getPermissionMethodAnnotation
+import com.google.android.lint.hasPermissionNameAnnotation
+import com.google.android.lint.isPermissionMethodCall
+import com.intellij.psi.PsiClassType
+import com.intellij.psi.PsiType
+import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
+import org.jetbrains.uast.UBinaryExpression
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UExpressionList
+import org.jetbrains.uast.UIfExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UThrowExpression
+import org.jetbrains.uast.UastBinaryOperator
+import org.jetbrains.uast.evaluateString
+import org.jetbrains.uast.skipParenthesizedExprDown
+import org.jetbrains.uast.visitor.AbstractUastVisitor
+
+/**
+ * Helper class that facilitates the creation of lint auto fixes
+ */
+data class EnforcePermissionFix(
+    val manualCheckLocations: List<Location>,
+    val permissionNames: List<String>,
+    val errorLevel: Boolean,
+    val anyOf: Boolean,
+) {
+    fun toLintFix(context: JavaContext, node: UMethod): LintFix {
+        val methodLocation = context.getLocation(node)
+        val replaceOrRemoveFixes = manualCheckLocations.mapIndexed { index, manualCheckLocation ->
+            if (index == 0) {
+                // Replace the first manual check with a call to the helper method
+                getHelperMethodFix(node, manualCheckLocation, false)
+            } else {
+                // Remove all subsequent manual checks
+                LintFix.create()
+                    .replace()
+                    .reformat(true)
+                    .range(manualCheckLocation)
+                    .with("")
+                    .autoFix()
+                    .build()
+            }
+        }
+
+        // Annotate the method with @EnforcePermission(...)
+        val annotateFix = LintFix.create()
+            .annotate(annotation)
+            .range(methodLocation)
+            .autoFix()
+            .build()
+
+        return LintFix.create().composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
+    }
+
+    private val annotation: String
+        get() {
+            val quotedPermissions = permissionNames.joinToString(", ") { """"$it"""" }
+
+            val attributeName =
+                if (permissionNames.size > 1) {
+                    if (anyOf) "anyOf" else "allOf"
+                } else null
+
+            val annotationParameter =
+                if (attributeName != null) "$attributeName={$quotedPermissions}"
+                else quotedPermissions
+
+            return "@$ANNOTATION_ENFORCE_PERMISSION($annotationParameter)"
+        }
+
+    companion object {
+        /**
+         * Walks the expressions in a block, looking for simple permission checks.
+         *
+         * As soon as something other than a permission check is encountered, stop looking,
+         * as some other business logic is happening that prevents an automated fix.
+         */
+        fun fromBlockExpression(
+            context: JavaContext,
+            blockExpression: UBlockExpression
+        ): EnforcePermissionFix? {
+            try {
+                val singleFixes = mutableListOf<EnforcePermissionFix>()
+                for (expression in blockExpression.expressions) {
+                    val fix = fromExpression(context, expression) ?: break
+                    singleFixes.add(fix)
+                }
+                return compose(singleFixes)
+            } catch (e: AnyOfAllOfException) {
+                return null
+            }
+        }
+
+        /**
+         * Conditionally constructs EnforcePermissionFix from any UExpression
+         *
+         * @return EnforcePermissionFix if the expression boils down to a permission check,
+         * else null
+         */
+        fun fromExpression(
+            context: JavaContext,
+            expression: UExpression
+        ): EnforcePermissionFix? {
+            val trimmedExpression = expression.skipParenthesizedExprDown()
+            if (trimmedExpression is UIfExpression) {
+                return fromIfExpression(context, trimmedExpression)
+            }
+            findCallExpression(trimmedExpression)?.let {
+                return fromCallExpression(context, it)
+            }
+            return null
+        }
+
+        /**
+         * Conditionally constructs EnforcePermissionFix from a UCallExpression
+         *
+         * @return EnforcePermissionFix if the called method is annotated with @PermissionMethod, else null
+         */
+        fun fromCallExpression(
+            context: JavaContext,
+            callExpression: UCallExpression
+        ): EnforcePermissionFix? {
+            val method = callExpression.resolve()?.getUMethod() ?: return null
+            val annotation = getPermissionMethodAnnotation(method) ?: return null
+            val returnsVoid = method.returnType == PsiType.VOID
+            val orSelf = getAnnotationBooleanValue(annotation, "orSelf") ?: false
+            val anyOf = getAnnotationBooleanValue(annotation, "anyOf") ?: false
+            return EnforcePermissionFix(
+                    listOf(getPermissionCheckLocation(context, callExpression)),
+                    getPermissionCheckValues(callExpression),
+                    errorLevel = isErrorLevel(throws = returnsVoid, orSelf = orSelf),
+                    anyOf,
+            )
+        }
+
+        /**
+         * Conditionally constructs EnforcePermissionFix from a UCallExpression
+         *
+         * @return EnforcePermissionFix IF AND ONLY IF:
+         * * The condition of the if statement compares the return value of a
+         *   PermissionMethod to one of the PackageManager.PermissionResult values
+         * * The expression inside the if statement does nothing but throw SecurityException
+         */
+        fun fromIfExpression(
+            context: JavaContext,
+            ifExpression: UIfExpression
+        ): EnforcePermissionFix? {
+            val condition = ifExpression.condition.skipParenthesizedExprDown()
+            if (condition !is UBinaryExpression) return null
+
+            val maybeLeftCall = findCallExpression(condition.leftOperand)
+            val maybeRightCall = findCallExpression(condition.rightOperand)
+
+            val (callExpression, comparison) =
+                    if (maybeLeftCall is UCallExpression) {
+                        Pair(maybeLeftCall, condition.rightOperand)
+                    } else if (maybeRightCall is UCallExpression) {
+                        Pair(maybeRightCall, condition.leftOperand)
+                    } else return null
+
+            val permissionMethodAnnotation = getPermissionMethodAnnotation(
+                    callExpression.resolve()?.getUMethod()) ?: return null
+
+            val equalityCheck =
+                    when (comparison.findSelector().asSourceString()
+                            .filterNot(Char::isWhitespace)) {
+                        "PERMISSION_GRANTED" -> UastBinaryOperator.IDENTITY_NOT_EQUALS
+                        "PERMISSION_DENIED" -> UastBinaryOperator.IDENTITY_EQUALS
+                        else -> return null
+                    }
+
+            if (condition.operator != equalityCheck) return null
+
+            val throwExpression: UThrowExpression? =
+                    ifExpression.thenExpression as? UThrowExpression
+                            ?: (ifExpression.thenExpression as? UBlockExpression)
+                                    ?.expressions?.firstOrNull()
+                                    as? UThrowExpression
+
+
+            val thrownClass = (throwExpression?.thrownExpression?.getExpressionType()
+                    as? PsiClassType)?.resolve() ?: return null
+            if (!context.evaluator.inheritsFrom(
+                            thrownClass, "java.lang.SecurityException")){
+                return null
+            }
+
+            val orSelf = getAnnotationBooleanValue(permissionMethodAnnotation, "orSelf") ?: false
+            val anyOf = getAnnotationBooleanValue(permissionMethodAnnotation, "anyOf") ?: false
+
+            return EnforcePermissionFix(
+                    listOf(context.getLocation(ifExpression)),
+                    getPermissionCheckValues(callExpression),
+                    errorLevel = isErrorLevel(throws = true, orSelf = orSelf),
+                    anyOf = anyOf
+            )
+        }
+
+
+        fun compose(individuals: List<EnforcePermissionFix>): EnforcePermissionFix? {
+            if (individuals.isEmpty()) return null
+            val anyOfs = individuals.filter(EnforcePermissionFix::anyOf)
+            // anyOf/allOf should be consistent.  If we encounter some @PermissionMethods that are anyOf
+            // and others that aren't, we don't know what to do.
+            if (anyOfs.isNotEmpty() && anyOfs.size < individuals.size) {
+                throw AnyOfAllOfException()
+            }
+            return EnforcePermissionFix(
+                    individuals.flatMap(EnforcePermissionFix::manualCheckLocations),
+                    individuals.flatMap(EnforcePermissionFix::permissionNames),
+                    errorLevel = individuals.all(EnforcePermissionFix::errorLevel),
+                    anyOf = anyOfs.isNotEmpty()
+            )
+        }
+
+        /**
+         * Given a permission check, get its proper location
+         * so that a lint fix can remove the entire expression
+         */
+        private fun getPermissionCheckLocation(
+            context: JavaContext,
+            callExpression: UCallExpression
+        ):
+                Location {
+            val javaPsi = callExpression.javaPsi!!
+            return Location.create(
+                context.file,
+                javaPsi.containingFile?.text,
+                javaPsi.textRange.startOffset,
+                // unfortunately the element doesn't include the ending semicolon
+                javaPsi.textRange.endOffset + 1
+            )
+        }
+
+        /**
+         * Given a @PermissionMethod, find arguments annotated with @PermissionName
+         * and pull out the permission value(s) being used.  Also evaluates nested calls
+         * to @PermissionMethod(s) in the given method's body.
+         */
+        @Throws(AnyOfAllOfException::class)
+        private fun getPermissionCheckValues(
+            callExpression: UCallExpression
+        ): List<String> {
+            if (!isPermissionMethodCall(callExpression)) return emptyList()
+
+            val result = mutableSetOf<String>() // protect against duplicate permission values
+            val visitedCalls = mutableSetOf<UCallExpression>() // don't visit the same call twice
+            val bfsQueue = ArrayDeque(listOf(callExpression))
+
+            var anyOfAllOfState: AnyOfAllOfState = AnyOfAllOfState.INITIAL
+
+            // Bread First Search - evaluating nested @PermissionMethod(s) in the available
+            // source code for @PermissionName(s).
+            while (bfsQueue.isNotEmpty()) {
+                val currentCallExpression = bfsQueue.removeFirst()
+                visitedCalls.add(currentCallExpression)
+                val currentPermissions = findPermissions(currentCallExpression)
+                result.addAll(currentPermissions)
+
+                val currentAnnotation = getPermissionMethodAnnotation(
+                        currentCallExpression.resolve()?.getUMethod())
+                val currentAnyOf = getAnnotationBooleanValue(currentAnnotation, "anyOf") ?: false
+
+                // anyOf/allOf should be consistent.  If we encounter a nesting of @PermissionMethods
+                // where we start in an anyOf state and switch to allOf, or vice versa,
+                // we don't know what to do.
+                if (anyOfAllOfState == AnyOfAllOfState.INITIAL) {
+                    if (currentAnyOf) anyOfAllOfState = AnyOfAllOfState.ANY_OF
+                    else if (result.isNotEmpty()) anyOfAllOfState = AnyOfAllOfState.ALL_OF
+                }
+
+                if (anyOfAllOfState == AnyOfAllOfState.ALL_OF && currentAnyOf) {
+                    throw AnyOfAllOfException()
+                }
+
+                if (anyOfAllOfState == AnyOfAllOfState.ANY_OF &&
+                        !currentAnyOf && currentPermissions.size > 1) {
+                    throw AnyOfAllOfException()
+                }
+
+                currentCallExpression.resolve()?.getUMethod()
+                        ?.accept(PermissionCheckValuesVisitor(visitedCalls, bfsQueue))
+            }
+
+            return result.toList()
+        }
+
+        private enum class AnyOfAllOfState {
+            INITIAL,
+            ANY_OF,
+            ALL_OF
+        }
+
+        /**
+         * Adds visited permission method calls to the provided
+         * queue in support of the BFS traversal happening while
+         * this is used
+         */
+        private class PermissionCheckValuesVisitor(
+                val visitedCalls: Set<UCallExpression>,
+                val bfsQueue: ArrayDeque<UCallExpression>
+        ) : AbstractUastVisitor() {
+            override fun visitCallExpression(node: UCallExpression): Boolean {
+                if (isPermissionMethodCall(node) && node !in visitedCalls) {
+                    bfsQueue.add(node)
+                }
+                return false
+            }
+        }
+
+        private fun findPermissions(
+            callExpression: UCallExpression,
+        ): List<String> {
+            val annotation = getPermissionMethodAnnotation(callExpression.resolve()?.getUMethod())
+
+            val hardCodedPermissions = (getAnnotationStringValues(annotation, "value")
+                    ?: emptyArray())
+                    .toList()
+
+            val indices = callExpression.resolve()?.getUMethod()
+                    ?.uastParameters
+                    ?.filter(::hasPermissionNameAnnotation)
+                    ?.mapNotNull { it.sourcePsi?.parameterIndex() }
+                    ?: emptyList()
+
+            val argPermissions = indices
+                    .flatMap { i ->
+                        when (val argument = callExpression.getArgumentForParameter(i)) {
+                            null -> listOf(null)
+                            is UExpressionList -> // varargs e.g. someMethod(String...)
+                                argument.expressions.map(UExpression::evaluateString)
+                            else -> listOf(argument.evaluateString())
+                        }
+                    }
+                    .filterNotNull()
+
+            return hardCodedPermissions + argPermissions
+        }
+
+        /**
+         * If we detect that the PermissionMethod enforces that permission is granted,
+         * AND is of the "orSelf" variety, we are very confident that this is a behavior
+         * preserving migration to @EnforcePermission.  Thus, the incident should be ERROR
+         * level.
+         */
+        private fun isErrorLevel(throws: Boolean, orSelf: Boolean): Boolean = throws && orSelf
+    }
+}
+/**
+ * anyOf/allOf @PermissionMethods must be consistent to apply @EnforcePermission -
+ * meaning if we encounter some @PermissionMethods that are anyOf, and others are allOf,
+ * we don't know which to apply.
+ */
+class AnyOfAllOfException : Exception() {
+    override val message: String = "anyOf/allOf permission methods cannot be mixed"
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
new file mode 100644
index 0000000..df13af5
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.aidl
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.google.android.lint.findCallExpression
+import com.intellij.psi.PsiElement
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UDeclarationsExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.skipParenthesizedExprDown
+
+class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+            listOf(UMethod::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitMethod(node: UMethod) {
+            if (context.evaluator.isAbstract(node)) return
+            if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
+
+            if (!isContainedInSubclassOfStub(context, node)) {
+                context.report(
+                    ISSUE_MISUSING_ENFORCE_PERMISSION,
+                    node,
+                    context.getLocation(node),
+                    "The class of ${node.name} does not inherit from an AIDL generated Stub class"
+                )
+                return
+            }
+
+            val targetExpression = getHelperMethodCallSourceString(node)
+            val message =
+                "Method must start with $targetExpression or super.${node.name}(), if applicable"
+
+            val firstExpression = (node.uastBody as? UBlockExpression)
+                    ?.expressions?.firstOrNull()
+
+            if (firstExpression == null) {
+                context.report(
+                    ISSUE_ENFORCE_PERMISSION_HELPER,
+                    context.getLocation(node),
+                    message,
+                )
+                return
+            }
+
+            val firstExpressionSource = firstExpression.skipParenthesizedExprDown()
+              .asSourceString()
+              .filterNot(Char::isWhitespace)
+
+            if (firstExpressionSource != targetExpression &&
+                  firstExpressionSource != "super.$targetExpression") {
+                // calling super.<methodName>() is also legal
+                val directSuper = context.evaluator.getSuperMethod(node)
+                val firstCall = findCallExpression(firstExpression)?.resolve()
+                if (directSuper != null && firstCall == directSuper) return
+
+                val locationTarget = getLocationTarget(firstExpression)
+                val expressionLocation = context.getLocation(locationTarget)
+
+                context.report(
+                    ISSUE_ENFORCE_PERMISSION_HELPER,
+                    context.getLocation(node),
+                    message,
+                    getHelperMethodFix(node, expressionLocation),
+                )
+            }
+        }
+    }
+
+    companion object {
+        private const val HELPER_SUFFIX = "_enforcePermission"
+
+        private const val EXPLANATION = """
+            The @EnforcePermission annotation can only be used on methods whose class extends from
+            the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the
+            AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX.
+
+            yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can
+            either call it directly, or call it indirectly via super.yourMethodName().
+            """
+
+        val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
+                id = "MissingEnforcePermissionHelper",
+                briefDescription = """Missing permission-enforcing method call in AIDL method
+                    |annotated with @EnforcePermission""".trimMargin(),
+                explanation = EXPLANATION,
+                category = Category.SECURITY,
+                priority = 6,
+                severity = Severity.ERROR,
+                implementation = Implementation(
+                        EnforcePermissionHelperDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                )
+        )
+
+        val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create(
+                id = "MisusingEnforcePermissionAnnotation",
+                briefDescription = "@EnforcePermission cannot be used here",
+                explanation = EXPLANATION,
+                category = Category.SECURITY,
+                priority = 6,
+                severity = Severity.ERROR,
+                implementation = Implementation(
+                        EnforcePermissionDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                )
+        )
+
+        /**
+         * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
+         * resulting in an incorrect Location if used directly
+         */
+        private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
+            if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
+            if (firstExpression is UDeclarationsExpression) {
+                return firstExpression.declarations.firstOrNull()?.sourcePsi
+            }
+            return null
+        }
+    }
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
new file mode 100644
index 0000000..d41fee3
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Location
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiReferenceList
+import org.jetbrains.uast.UMethod
+
+/**
+ * Given a UMethod, determine if this method is
+ * the entrypoint to an interface generated by AIDL,
+ * returning the interface name if so, otherwise returning null
+ */
+fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? {
+    if (!isContainedInSubclassOfStub(context, node)) return null
+    for (superMethod in node.findSuperMethods()) {
+        for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
+            ?: continue) {
+            if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
+                return superMethod.containingClass?.name
+            }
+        }
+    }
+    return null
+}
+
+fun isContainedInSubclassOfStub(context: JavaContext, node: UMethod?): Boolean {
+    var superClass = node?.containingClass?.superClass
+    while (superClass != null) {
+        if (isStub(context, superClass)) return true
+        superClass = superClass.superClass
+    }
+    return false
+}
+
+fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean {
+    if (psiClass == null) return false
+    if (psiClass.name != "Stub") return false
+    if (!context.evaluator.isStatic(psiClass)) return false
+    if (!context.evaluator.isAbstract(psiClass)) return false
+
+    if (!hasSingleAncestor(psiClass.extendsList, BINDER_CLASS)) return false
+
+    val parent = psiClass.parent as? PsiClass ?: return false
+    if (!hasSingleAncestor(parent.extendsList, IINTERFACE_INTERFACE)) return false
+
+    val parentName = parent.qualifiedName ?: return false
+    if (!hasSingleAncestor(psiClass.implementsList, parentName)) return false
+
+    return true
+}
+
+private fun hasSingleAncestor(references: PsiReferenceList?, qualifiedName: String) =
+        references != null &&
+                references.referenceElements.size == 1 &&
+                references.referenceElements[0].qualifiedName == qualifiedName
+
+fun getHelperMethodCallSourceString(node: UMethod) = "${node.name}$AIDL_PERMISSION_HELPER_SUFFIX()"
+
+fun getHelperMethodFix(
+    node: UMethod,
+    manualCheckLocation: Location,
+    prepend: Boolean = true
+): LintFix {
+    val helperMethodSource = getHelperMethodCallSourceString(node)
+    val indent = " ".repeat(manualCheckLocation.start?.column ?: 0)
+    val newText = "$helperMethodSource;${if (prepend) "\n\n$indent" else ""}"
+
+    val fix = LintFix.create()
+            .replace()
+            .range(manualCheckLocation)
+            .with(newText)
+            .reformat(true)
+            .autoFix()
+
+    if (prepend) fix.beginning()
+
+    return fix.build()
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
new file mode 100644
index 0000000..c7be36e
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UMethod
+
+/**
+ * Looks for methods implementing generated AIDL interface stubs
+ * that can have simple permission checks migrated to
+ * @EnforcePermission annotations
+ */
+@Suppress("UnstableApiUsage")
+class SimpleManualPermissionEnforcementDetector : AidlImplementationDetector() {
+    override fun visitAidlMethod(
+            context: JavaContext,
+            node: UMethod,
+            interfaceName: String,
+            body: UBlockExpression
+    ) {
+        val enforcePermissionFix = EnforcePermissionFix.fromBlockExpression(context, body) ?: return
+        val lintFix = enforcePermissionFix.toLintFix(context, node)
+        val message =
+                "$interfaceName permission check ${
+                    if (enforcePermissionFix.errorLevel) "should" else "can"
+                } be converted to @EnforcePermission annotation"
+
+        val incident = Incident(
+                ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT,
+                enforcePermissionFix.manualCheckLocations.last(),
+                message,
+                lintFix
+        )
+
+        // TODO(b/265014041): turn on errors once all code that would cause one is fixed
+        // if (enforcePermissionFix.errorLevel) {
+        //     incident.overrideSeverity(Severity.ERROR)
+        // }
+
+        context.report(incident)
+    }
+
+    companion object {
+
+        private val EXPLANATION = """
+            Whenever possible, method implementations of AIDL interfaces should use the @EnforcePermission
+            annotation to declare the permissions to be enforced.  The verification code is then
+            generated by the AIDL compiler, which also takes care of annotating the generated java
+            code.
+
+            This reduces the risk of bugs around these permission checks (that often become vulnerabilities).
+            It also enables easier auditing and review.
+
+            Please migrate to an @EnforcePermission annotation. (See: go/aidl-enforce-howto)
+        """.trimIndent()
+
+        @JvmField
+        val ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT = Issue.create(
+                id = "SimpleManualPermissionEnforcement",
+                briefDescription = "Manual permission check can be @EnforcePermission annotation",
+                explanation = EXPLANATION,
+                category = Category.SECURITY,
+                priority = 5,
+                severity = Severity.WARNING,
+                implementation = Implementation(
+                        SimpleManualPermissionEnforcementDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                ),
+        )
+    }
+}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorCodegenTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorCodegenTest.kt
new file mode 100644
index 0000000..f2930d9
--- /dev/null
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorCodegenTest.kt
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class EnforcePermissionDetectorCodegenTest : LintDetectorTest() {
+    override fun getDetector(): Detector = EnforcePermissionDetector()
+
+    override fun getIssues(): List<Issue> = listOf(
+            EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
+            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    fun test_generated_IProtected() {
+        lint().files(
+            java(
+                """
+                /*
+                 * This file is auto-generated.  DO NOT MODIFY.
+                 */
+                package android.aidl.tests.permission;
+                public interface IProtected extends android.os.IInterface
+                {
+                  /** Default implementation for IProtected. */
+                  public static class Default implements android.aidl.tests.permission.IProtected
+                  {
+                    @Override public void PermissionProtected() throws android.os.RemoteException
+                    {
+                    }
+                    @Override public void MultiplePermissionsAll() throws android.os.RemoteException
+                    {
+                    }
+                    @Override public void MultiplePermissionsAny() throws android.os.RemoteException
+                    {
+                    }
+                    @Override public void NonManifestPermission() throws android.os.RemoteException
+                    {
+                    }
+                    // Used by the integration tests to dynamically set permissions that are considered granted.
+                    @Override public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException
+                    {
+                    }
+                    @Override
+                    public android.os.IBinder asBinder() {
+                      return null;
+                    }
+                  }
+                  /** Local-side IPC implementation stub class. */
+                  public static abstract class Stub extends android.os.Binder implements android.aidl.tests.permission.IProtected
+                  {
+                    private final android.os.PermissionEnforcer mEnforcer;
+                    /** Construct the stub using the Enforcer provided. */
+                    public Stub(android.os.PermissionEnforcer enforcer)
+                    {
+                      this.attachInterface(this, DESCRIPTOR);
+                      if (enforcer == null) {
+                        throw new IllegalArgumentException("enforcer cannot be null");
+                      }
+                      mEnforcer = enforcer;
+                    }
+                    @Deprecated
+                    /** Default constructor. */
+                    public Stub() {
+                      this(android.os.PermissionEnforcer.fromContext(
+                         android.app.ActivityThread.currentActivityThread().getSystemContext()));
+                    }
+                    /**
+                     * Cast an IBinder object into an android.aidl.tests.permission.IProtected interface,
+                     * generating a proxy if needed.
+                     */
+                    public static android.aidl.tests.permission.IProtected asInterface(android.os.IBinder obj)
+                    {
+                      if ((obj==null)) {
+                        return null;
+                      }
+                      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+                      if (((iin!=null)&&(iin instanceof android.aidl.tests.permission.IProtected))) {
+                        return ((android.aidl.tests.permission.IProtected)iin);
+                      }
+                      return new android.aidl.tests.permission.IProtected.Stub.Proxy(obj);
+                    }
+                    @Override public android.os.IBinder asBinder()
+                    {
+                      return this;
+                    }
+                    /** @hide */
+                    public static java.lang.String getDefaultTransactionName(int transactionCode)
+                    {
+                      switch (transactionCode)
+                      {
+                        case TRANSACTION_PermissionProtected:
+                        {
+                          return "PermissionProtected";
+                        }
+                        case TRANSACTION_MultiplePermissionsAll:
+                        {
+                          return "MultiplePermissionsAll";
+                        }
+                        case TRANSACTION_MultiplePermissionsAny:
+                        {
+                          return "MultiplePermissionsAny";
+                        }
+                        case TRANSACTION_NonManifestPermission:
+                        {
+                          return "NonManifestPermission";
+                        }
+                        case TRANSACTION_SetGranted:
+                        {
+                          return "SetGranted";
+                        }
+                        default:
+                        {
+                          return null;
+                        }
+                      }
+                    }
+                    /** @hide */
+                    public java.lang.String getTransactionName(int transactionCode)
+                    {
+                      return this.getDefaultTransactionName(transactionCode);
+                    }
+                    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
+                    {
+                      java.lang.String descriptor = DESCRIPTOR;
+                      if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
+                        data.enforceInterface(descriptor);
+                      }
+                      switch (code)
+                      {
+                        case INTERFACE_TRANSACTION:
+                        {
+                          reply.writeString(descriptor);
+                          return true;
+                        }
+                      }
+                      switch (code)
+                      {
+                        case TRANSACTION_PermissionProtected:
+                        {
+                          this.PermissionProtected();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_MultiplePermissionsAll:
+                        {
+                          this.MultiplePermissionsAll();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_MultiplePermissionsAny:
+                        {
+                          this.MultiplePermissionsAny();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_NonManifestPermission:
+                        {
+                          this.NonManifestPermission();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_SetGranted:
+                        {
+                          java.util.List<java.lang.String> _arg0;
+                          _arg0 = data.createStringArrayList();
+                          data.enforceNoDataAvail();
+                          this.SetGranted(_arg0);
+                          reply.writeNoException();
+                          break;
+                        }
+                        default:
+                        {
+                          return super.onTransact(code, data, reply, flags);
+                        }
+                      }
+                      return true;
+                    }
+                    private static class Proxy implements android.aidl.tests.permission.IProtected
+                    {
+                      private android.os.IBinder mRemote;
+                      Proxy(android.os.IBinder remote)
+                      {
+                        mRemote = remote;
+                      }
+                      @Override public android.os.IBinder asBinder()
+                      {
+                        return mRemote;
+                      }
+                      public java.lang.String getInterfaceDescriptor()
+                      {
+                        return DESCRIPTOR;
+                      }
+                      @Override public void PermissionProtected() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_PermissionProtected, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      @Override public void MultiplePermissionsAll() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_MultiplePermissionsAll, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      @Override public void MultiplePermissionsAny() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_MultiplePermissionsAny, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      @Override public void NonManifestPermission() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_NonManifestPermission, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      // Used by the integration tests to dynamically set permissions that are considered granted.
+                      @Override public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          _data.writeStringList(permissions);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_SetGranted, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                    }
+                    static final int TRANSACTION_PermissionProtected = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
+                    /** Helper method to enforce permissions for PermissionProtected */
+                    protected void PermissionProtected_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermission(android.Manifest.permission.READ_PHONE_STATE, source);
+                    }
+                    static final int TRANSACTION_MultiplePermissionsAll = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
+                    /** Helper method to enforce permissions for MultiplePermissionsAll */
+                    protected void MultiplePermissionsAll_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermissionAllOf(new String[]{android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}, source);
+                    }
+                    static final int TRANSACTION_MultiplePermissionsAny = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
+                    /** Helper method to enforce permissions for MultiplePermissionsAny */
+                    protected void MultiplePermissionsAny_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermissionAnyOf(new String[]{android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}, source);
+                    }
+                    static final int TRANSACTION_NonManifestPermission = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
+                    /** Helper method to enforce permissions for NonManifestPermission */
+                    protected void NonManifestPermission_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, source);
+                    }
+                    static final int TRANSACTION_SetGranted = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
+                    /** @hide */
+                    public int getMaxTransactionId()
+                    {
+                      return 4;
+                    }
+                  }
+                  
+                  @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                  public void PermissionProtected() throws android.os.RemoteException;
+                  @android.annotation.EnforcePermission(allOf = {android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE})
+                  public void MultiplePermissionsAll() throws android.os.RemoteException;
+                  @android.annotation.EnforcePermission(anyOf = {android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE})
+                  public void MultiplePermissionsAny() throws android.os.RemoteException;
+                  @android.annotation.EnforcePermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+                  public void NonManifestPermission() throws android.os.RemoteException;
+                  // Used by the integration tests to dynamically set permissions that are considered granted.
+                  @android.annotation.RequiresNoPermission
+                  public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException;
+                }
+                """
+            ).indented(),
+                *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun test_generated_IProtectedInterface() {
+        lint().files(
+                java(
+                    """
+                    /*
+                     * This file is auto-generated.  DO NOT MODIFY.
+                     */
+                    package android.aidl.tests.permission;
+                    public interface IProtectedInterface extends android.os.IInterface
+                    {
+                      /** Default implementation for IProtectedInterface. */
+                      public static class Default implements android.aidl.tests.permission.IProtectedInterface
+                      {
+                        @Override public void Method1() throws android.os.RemoteException
+                        {
+                        }
+                        @Override public void Method2() throws android.os.RemoteException
+                        {
+                        }
+                        @Override
+                        public android.os.IBinder asBinder() {
+                          return null;
+                        }
+                      }
+                      /** Local-side IPC implementation stub class. */
+                      public static abstract class Stub extends android.os.Binder implements android.aidl.tests.permission.IProtectedInterface
+                      {
+                        private final android.os.PermissionEnforcer mEnforcer;
+                        /** Construct the stub using the Enforcer provided. */
+                        public Stub(android.os.PermissionEnforcer enforcer)
+                        {
+                          this.attachInterface(this, DESCRIPTOR);
+                          if (enforcer == null) {
+                            throw new IllegalArgumentException("enforcer cannot be null");
+                          }
+                          mEnforcer = enforcer;
+                        }
+                        @Deprecated
+                        /** Default constructor. */
+                        public Stub() {
+                          this(android.os.PermissionEnforcer.fromContext(
+                             android.app.ActivityThread.currentActivityThread().getSystemContext()));
+                        }
+                        /**
+                         * Cast an IBinder object into an android.aidl.tests.permission.IProtectedInterface interface,
+                         * generating a proxy if needed.
+                         */
+                        public static android.aidl.tests.permission.IProtectedInterface asInterface(android.os.IBinder obj)
+                        {
+                          if ((obj==null)) {
+                            return null;
+                          }
+                          android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+                          if (((iin!=null)&&(iin instanceof android.aidl.tests.permission.IProtectedInterface))) {
+                            return ((android.aidl.tests.permission.IProtectedInterface)iin);
+                          }
+                          return new android.aidl.tests.permission.IProtectedInterface.Stub.Proxy(obj);
+                        }
+                        @Override public android.os.IBinder asBinder()
+                        {
+                          return this;
+                        }
+                        /** @hide */
+                        public static java.lang.String getDefaultTransactionName(int transactionCode)
+                        {
+                          switch (transactionCode)
+                          {
+                            case TRANSACTION_Method1:
+                            {
+                              return "Method1";
+                            }
+                            case TRANSACTION_Method2:
+                            {
+                              return "Method2";
+                            }
+                            default:
+                            {
+                              return null;
+                            }
+                          }
+                        }
+                        /** @hide */
+                        public java.lang.String getTransactionName(int transactionCode)
+                        {
+                          return this.getDefaultTransactionName(transactionCode);
+                        }
+                        @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
+                        {
+                          java.lang.String descriptor = DESCRIPTOR;
+                          if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
+                            data.enforceInterface(descriptor);
+                          }
+                          switch (code)
+                          {
+                            case INTERFACE_TRANSACTION:
+                            {
+                              reply.writeString(descriptor);
+                              return true;
+                            }
+                          }
+                          switch (code)
+                          {
+                            case TRANSACTION_Method1:
+                            {
+                              this.Method1();
+                              reply.writeNoException();
+                              break;
+                            }
+                            case TRANSACTION_Method2:
+                            {
+                              this.Method2();
+                              reply.writeNoException();
+                              break;
+                            }
+                            default:
+                            {
+                              return super.onTransact(code, data, reply, flags);
+                            }
+                          }
+                          return true;
+                        }
+                        private static class Proxy implements android.aidl.tests.permission.IProtectedInterface
+                        {
+                          private android.os.IBinder mRemote;
+                          Proxy(android.os.IBinder remote)
+                          {
+                            mRemote = remote;
+                          }
+                          @Override public android.os.IBinder asBinder()
+                          {
+                            return mRemote;
+                          }
+                          public java.lang.String getInterfaceDescriptor()
+                          {
+                            return DESCRIPTOR;
+                          }
+                          @Override public void Method1() throws android.os.RemoteException
+                          {
+                            android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                            android.os.Parcel _reply = android.os.Parcel.obtain();
+                            try {
+                              _data.writeInterfaceToken(DESCRIPTOR);
+                              boolean _status = mRemote.transact(Stub.TRANSACTION_Method1, _data, _reply, 0);
+                              _reply.readException();
+                            }
+                            finally {
+                              _reply.recycle();
+                              _data.recycle();
+                            }
+                          }
+                          @Override public void Method2() throws android.os.RemoteException
+                          {
+                            android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                            android.os.Parcel _reply = android.os.Parcel.obtain();
+                            try {
+                              _data.writeInterfaceToken(DESCRIPTOR);
+                              boolean _status = mRemote.transact(Stub.TRANSACTION_Method2, _data, _reply, 0);
+                              _reply.readException();
+                            }
+                            finally {
+                              _reply.recycle();
+                              _data.recycle();
+                            }
+                          }
+                        }
+                        static final int TRANSACTION_Method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
+                        /** Helper method to enforce permissions for Method1 */
+                        protected void Method1_enforcePermission() throws SecurityException {
+                          android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                          mEnforcer.enforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION, source);
+                        }
+                        static final int TRANSACTION_Method2 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
+                        /** Helper method to enforce permissions for Method2 */
+                        protected void Method2_enforcePermission() throws SecurityException {
+                          android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                          mEnforcer.enforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION, source);
+                        }
+                        /** @hide */
+                        public int getMaxTransactionId()
+                        {
+                          return 1;
+                        }
+                      }
+                      
+                      @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+                      public void Method1() throws android.os.RemoteException;
+                      @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+                      public void Method2() throws android.os.RemoteException;
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expectClean()
+    }
+
+    /* Stubs */
+
+    private val manifestPermissionStub: TestFile = java(
+        """
+        package android.Manifest;
+        class permission {
+          public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+          public static final String INTERNET = "android.permission.INTERNET";
+        }
+        """
+    ).indented()
+
+    private val enforcePermissionAnnotationStub: TestFile = java(
+        """
+        package android.annotation;
+        public @interface EnforcePermission {}
+        """
+    ).indented()
+
+    private val stubs = arrayOf(manifestPermissionStub, enforcePermissionAnnotationStub)
+}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
new file mode 100644
index 0000000..75b0073
--- /dev/null
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class EnforcePermissionDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = EnforcePermissionDetector()
+
+    override fun getIssues(): List<Issue> = listOf(
+            EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
+            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    fun testDoesNotDetectIssuesCorrectAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass2 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                public void testMethod() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
+    }
+
+    fun testDoesNotDetectIssuesCorrectAnnotationAllOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass11 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAll() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
+    }
+
+    fun testDoesNotDetectIssuesCorrectAnnotationAllLiteralOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass111 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(allOf={"android.permission.INTERNET", android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAllLiteral() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
+    }
+
+    fun testDoesNotDetectIssuesCorrectAnnotationAnyOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass12 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAny() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
+    }
+
+    fun testDoesNotDetectIssuesCorrectAnnotationAnyLiteralOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass121 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(anyOf={"android.permission.INTERNET", android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAnyLiteral() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
+    }
+
+    fun testDetectIssuesMismatchingAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass4 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
+                public void testMethod() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
+                which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesEmptyAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass41 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission
+                public void testMethod() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass41.java:4: Error: The method TestClass41.testMethod is annotated with @android.annotation.EnforcePermission \
+                which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMismatchingAnyAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass9 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC})
+                public void testMethodAny() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass9.java:4: Error: The method TestClass9.testMethodAny is annotated with \
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) \
+                which differs from the overridden method Stub.testMethodAny: \
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAny() {}
+                                ~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMismatchingAnyLiteralAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass91 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(anyOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"})
+                public void testMethodAnyLiteral() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass91.java:4: Error: The method TestClass91.testMethodAnyLiteral is annotated with \
+                @android.annotation.EnforcePermission(anyOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) \
+                which differs from the overridden method Stub.testMethodAnyLiteral: \
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAnyLiteral() {}
+                                ~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMismatchingAllAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass10 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC})
+                public void testMethodAll() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass10.java:4: Error: The method TestClass10.testMethodAll is annotated with \
+                @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) \
+                which differs from the overridden method Stub.testMethodAll: \
+                @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAll() {}
+                                ~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMismatchingAllLiteralAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass101 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(allOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"})
+                public void testMethodAllLiteral() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass101.java:4: Error: The method TestClass101.testMethodAllLiteral is annotated with \
+                @android.annotation.EnforcePermission(allOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) \
+                which differs from the overridden method Stub.testMethodAllLiteral: \
+                @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAllLiteral() {}
+                                ~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMissingAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass6 extends IFooMethod.Stub {
+                public void testMethod() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod overrides the method Stub.testMethod which is annotated with @EnforcePermission. \
+                The same annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesExtraAnnotationMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass7 extends IBar.Stub {
+                @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
+                public void testMethod() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass7.java:4: Error: The method TestClass7.testMethod overrides the method Stub.testMethod which is not annotated with @EnforcePermission. \
+                The same annotation must be used on Stub.testMethod. Did you forget to annotate the AIDL definition? [MissingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMissingAnnotationOnMethodWhenClassIsCalledDefault() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class Default extends IFooMethod.Stub {
+                public void testMethod() {}
+            }
+            """).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/test/pkg/Default.java:3: Error: The method Default.testMethod \
+                overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same annotation must be used on Default.testMethod [MissingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings 
+                """.addLineContinuation()
+            )
+    }
+
+    fun testDoesDetectIssuesShortStringsNotAllowed() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass121 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
+                public void testMethodAnyLiteral() {}
+            }
+            """).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/test/pkg/TestClass121.java:6: Error: The method \
+                TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) \
+                which differs from the overridden method Stub.testMethodAnyLiteral: \
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAnyLiteral() {}
+                                ~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation()
+            )
+    }
+
+    /* Stubs */
+
+    // A service with permission annotation on the method.
+    private val interfaceIFooMethodStub: TestFile = java(
+        """
+        public interface IFooMethod extends android.os.IInterface {
+         public static abstract class Stub extends android.os.Binder implements IFooMethod {
+            @Override
+            @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+            public void testMethod() {}
+            @Override
+            @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+            public void testMethodAny() {}
+            @Override
+            @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+            public void testMethodAnyLiteral() {}
+            @Override
+            @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+            public void testMethodAll() {}
+            @Override
+            @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+            public void testMethodAllLiteral() {}
+          }
+          @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+          public void testMethod();
+          @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+          public void testMethodAny() {}
+          @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+          public void testMethodAnyLiteral() {}
+          @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+          public void testMethodAll() {}
+          @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+          public void testMethodAllLiteral() {}
+        }
+        """
+    ).indented()
+
+    // A service without any permission annotation.
+    private val interfaceIBarStub: TestFile = java(
+        """
+        public interface IBar extends android.os.IInterface {
+         public static abstract class Stub extends android.os.Binder implements IBar {
+            @Override
+            public void testMethod() {}
+          }
+          public void testMethod();
+        }
+        """
+    ).indented()
+
+    private val manifestPermissionStub: TestFile = java(
+        """
+        package android.Manifest;
+        class permission {
+          public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+          public static final String NFC = "android.permission.NFC";
+          public static final String INTERNET = "android.permission.INTERNET";
+        }
+        """
+    ).indented()
+
+    private val enforcePermissionAnnotationStub: TestFile = java(
+        """
+        package android.annotation;
+        public @interface EnforcePermission {}
+        """
+    ).indented()
+
+    private val stubs = arrayOf(interfaceIFooMethodStub, interfaceIBarStub,
+            manifestPermissionStub, enforcePermissionAnnotationStub)
+
+    // Substitutes "backslash + new line" with an empty string to imitate line continuation
+    private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")
+}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
new file mode 100644
index 0000000..5a63bb4
--- /dev/null
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class EnforcePermissionHelperDetectorCodegenTest : LintDetectorTest() {
+    override fun getDetector(): Detector = EnforcePermissionHelperDetector()
+
+    override fun getIssues(): List<Issue> = listOf(
+            EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    fun test_generated_IProtected() {
+        lint().testModes(TestMode.DEFAULT).files(
+            java(
+                """
+                /*
+                 * This file is auto-generated.  DO NOT MODIFY.
+                 */
+                package android.aidl.tests.permission;
+                public interface IProtected extends android.os.IInterface
+                {
+                  /** Default implementation for IProtected. */
+                  public static class Default implements android.aidl.tests.permission.IProtected
+                  {
+                    @Override public void PermissionProtected() throws android.os.RemoteException
+                    {
+                    }
+                    @Override public void MultiplePermissionsAll() throws android.os.RemoteException
+                    {
+                    }
+                    @Override public void MultiplePermissionsAny() throws android.os.RemoteException
+                    {
+                    }
+                    @Override public void NonManifestPermission() throws android.os.RemoteException
+                    {
+                    }
+                    // Used by the integration tests to dynamically set permissions that are considered granted.
+                    @Override public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException
+                    {
+                    }
+                    @Override
+                    public android.os.IBinder asBinder() {
+                      return null;
+                    }
+                  }
+                  /** Local-side IPC implementation stub class. */
+                  public static abstract class Stub extends android.os.Binder implements android.aidl.tests.permission.IProtected
+                  {
+                    private final android.os.PermissionEnforcer mEnforcer;
+                    /** Construct the stub using the Enforcer provided. */
+                    public Stub(android.os.PermissionEnforcer enforcer)
+                    {
+                      this.attachInterface(this, DESCRIPTOR);
+                      if (enforcer == null) {
+                        throw new IllegalArgumentException("enforcer cannot be null");
+                      }
+                      mEnforcer = enforcer;
+                    }
+                    @Deprecated
+                    /** Default constructor. */
+                    public Stub() {
+                      this(android.os.PermissionEnforcer.fromContext(
+                         android.app.ActivityThread.currentActivityThread().getSystemContext()));
+                    }
+                    /**
+                     * Cast an IBinder object into an android.aidl.tests.permission.IProtected interface,
+                     * generating a proxy if needed.
+                     */
+                    public static android.aidl.tests.permission.IProtected asInterface(android.os.IBinder obj)
+                    {
+                      if ((obj==null)) {
+                        return null;
+                      }
+                      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+                      if (((iin!=null)&&(iin instanceof android.aidl.tests.permission.IProtected))) {
+                        return ((android.aidl.tests.permission.IProtected)iin);
+                      }
+                      return new android.aidl.tests.permission.IProtected.Stub.Proxy(obj);
+                    }
+                    @Override public android.os.IBinder asBinder()
+                    {
+                      return this;
+                    }
+                    /** @hide */
+                    public static java.lang.String getDefaultTransactionName(int transactionCode)
+                    {
+                      switch (transactionCode)
+                      {
+                        case TRANSACTION_PermissionProtected:
+                        {
+                          return "PermissionProtected";
+                        }
+                        case TRANSACTION_MultiplePermissionsAll:
+                        {
+                          return "MultiplePermissionsAll";
+                        }
+                        case TRANSACTION_MultiplePermissionsAny:
+                        {
+                          return "MultiplePermissionsAny";
+                        }
+                        case TRANSACTION_NonManifestPermission:
+                        {
+                          return "NonManifestPermission";
+                        }
+                        case TRANSACTION_SetGranted:
+                        {
+                          return "SetGranted";
+                        }
+                        default:
+                        {
+                          return null;
+                        }
+                      }
+                    }
+                    /** @hide */
+                    public java.lang.String getTransactionName(int transactionCode)
+                    {
+                      return this.getDefaultTransactionName(transactionCode);
+                    }
+                    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
+                    {
+                      java.lang.String descriptor = DESCRIPTOR;
+                      if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
+                        data.enforceInterface(descriptor);
+                      }
+                      switch (code)
+                      {
+                        case INTERFACE_TRANSACTION:
+                        {
+                          reply.writeString(descriptor);
+                          return true;
+                        }
+                      }
+                      switch (code)
+                      {
+                        case TRANSACTION_PermissionProtected:
+                        {
+                          this.PermissionProtected();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_MultiplePermissionsAll:
+                        {
+                          this.MultiplePermissionsAll();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_MultiplePermissionsAny:
+                        {
+                          this.MultiplePermissionsAny();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_NonManifestPermission:
+                        {
+                          this.NonManifestPermission();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_SetGranted:
+                        {
+                          java.util.List<java.lang.String> _arg0;
+                          _arg0 = data.createStringArrayList();
+                          data.enforceNoDataAvail();
+                          this.SetGranted(_arg0);
+                          reply.writeNoException();
+                          break;
+                        }
+                        default:
+                        {
+                          return super.onTransact(code, data, reply, flags);
+                        }
+                      }
+                      return true;
+                    }
+                    private static class Proxy implements android.aidl.tests.permission.IProtected
+                    {
+                      private android.os.IBinder mRemote;
+                      Proxy(android.os.IBinder remote)
+                      {
+                        mRemote = remote;
+                      }
+                      @Override public android.os.IBinder asBinder()
+                      {
+                        return mRemote;
+                      }
+                      public java.lang.String getInterfaceDescriptor()
+                      {
+                        return DESCRIPTOR;
+                      }
+                      @Override public void PermissionProtected() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_PermissionProtected, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      @Override public void MultiplePermissionsAll() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_MultiplePermissionsAll, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      @Override public void MultiplePermissionsAny() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_MultiplePermissionsAny, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      @Override public void NonManifestPermission() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_NonManifestPermission, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      // Used by the integration tests to dynamically set permissions that are considered granted.
+                      @Override public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          _data.writeStringList(permissions);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_SetGranted, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                    }
+                    static final int TRANSACTION_PermissionProtected = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
+                    /** Helper method to enforce permissions for PermissionProtected */
+                    protected void PermissionProtected_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermission(android.Manifest.permission.READ_PHONE_STATE, source);
+                    }
+                    static final int TRANSACTION_MultiplePermissionsAll = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
+                    /** Helper method to enforce permissions for MultiplePermissionsAll */
+                    protected void MultiplePermissionsAll_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermissionAllOf(new String[]{android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}, source);
+                    }
+                    static final int TRANSACTION_MultiplePermissionsAny = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
+                    /** Helper method to enforce permissions for MultiplePermissionsAny */
+                    protected void MultiplePermissionsAny_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermissionAnyOf(new String[]{android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}, source);
+                    }
+                    static final int TRANSACTION_NonManifestPermission = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
+                    /** Helper method to enforce permissions for NonManifestPermission */
+                    protected void NonManifestPermission_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, source);
+                    }
+                    static final int TRANSACTION_SetGranted = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
+                    /** @hide */
+                    public int getMaxTransactionId()
+                    {
+                      return 4;
+                    }
+                  }
+                  
+                  @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                  public void PermissionProtected() throws android.os.RemoteException;
+                  @android.annotation.EnforcePermission(allOf = {android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE})
+                  public void MultiplePermissionsAll() throws android.os.RemoteException;
+                  @android.annotation.EnforcePermission(anyOf = {android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE})
+                  public void MultiplePermissionsAny() throws android.os.RemoteException;
+                  @android.annotation.EnforcePermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+                  public void NonManifestPermission() throws android.os.RemoteException;
+                  // Used by the integration tests to dynamically set permissions that are considered granted.
+                  @android.annotation.RequiresNoPermission
+                  public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException;
+                }
+                """
+            ).indented(),
+                *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun test_generated_IProtectedInterface() {
+        lint().files(
+                java(
+                    """
+                    /*
+                     * This file is auto-generated.  DO NOT MODIFY.
+                     */
+                    package android.aidl.tests.permission;
+                    public interface IProtectedInterface extends android.os.IInterface
+                    {
+                      /** Default implementation for IProtectedInterface. */
+                      public static class Default implements android.aidl.tests.permission.IProtectedInterface
+                      {
+                        @Override public void Method1() throws android.os.RemoteException
+                        {
+                        }
+                        @Override public void Method2() throws android.os.RemoteException
+                        {
+                        }
+                        @Override
+                        public android.os.IBinder asBinder() {
+                          return null;
+                        }
+                      }
+                      /** Local-side IPC implementation stub class. */
+                      public static abstract class Stub extends android.os.Binder implements android.aidl.tests.permission.IProtectedInterface
+                      {
+                        private final android.os.PermissionEnforcer mEnforcer;
+                        /** Construct the stub using the Enforcer provided. */
+                        public Stub(android.os.PermissionEnforcer enforcer)
+                        {
+                          this.attachInterface(this, DESCRIPTOR);
+                          if (enforcer == null) {
+                            throw new IllegalArgumentException("enforcer cannot be null");
+                          }
+                          mEnforcer = enforcer;
+                        }
+                        @Deprecated
+                        /** Default constructor. */
+                        public Stub() {
+                          this(android.os.PermissionEnforcer.fromContext(
+                             android.app.ActivityThread.currentActivityThread().getSystemContext()));
+                        }
+                        /**
+                         * Cast an IBinder object into an android.aidl.tests.permission.IProtectedInterface interface,
+                         * generating a proxy if needed.
+                         */
+                        public static android.aidl.tests.permission.IProtectedInterface asInterface(android.os.IBinder obj)
+                        {
+                          if ((obj==null)) {
+                            return null;
+                          }
+                          android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+                          if (((iin!=null)&&(iin instanceof android.aidl.tests.permission.IProtectedInterface))) {
+                            return ((android.aidl.tests.permission.IProtectedInterface)iin);
+                          }
+                          return new android.aidl.tests.permission.IProtectedInterface.Stub.Proxy(obj);
+                        }
+                        @Override public android.os.IBinder asBinder()
+                        {
+                          return this;
+                        }
+                        /** @hide */
+                        public static java.lang.String getDefaultTransactionName(int transactionCode)
+                        {
+                          switch (transactionCode)
+                          {
+                            case TRANSACTION_Method1:
+                            {
+                              return "Method1";
+                            }
+                            case TRANSACTION_Method2:
+                            {
+                              return "Method2";
+                            }
+                            default:
+                            {
+                              return null;
+                            }
+                          }
+                        }
+                        /** @hide */
+                        public java.lang.String getTransactionName(int transactionCode)
+                        {
+                          return this.getDefaultTransactionName(transactionCode);
+                        }
+                        @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
+                        {
+                          java.lang.String descriptor = DESCRIPTOR;
+                          if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
+                            data.enforceInterface(descriptor);
+                          }
+                          switch (code)
+                          {
+                            case INTERFACE_TRANSACTION:
+                            {
+                              reply.writeString(descriptor);
+                              return true;
+                            }
+                          }
+                          switch (code)
+                          {
+                            case TRANSACTION_Method1:
+                            {
+                              this.Method1();
+                              reply.writeNoException();
+                              break;
+                            }
+                            case TRANSACTION_Method2:
+                            {
+                              this.Method2();
+                              reply.writeNoException();
+                              break;
+                            }
+                            default:
+                            {
+                              return super.onTransact(code, data, reply, flags);
+                            }
+                          }
+                          return true;
+                        }
+                        private static class Proxy implements android.aidl.tests.permission.IProtectedInterface
+                        {
+                          private android.os.IBinder mRemote;
+                          Proxy(android.os.IBinder remote)
+                          {
+                            mRemote = remote;
+                          }
+                          @Override public android.os.IBinder asBinder()
+                          {
+                            return mRemote;
+                          }
+                          public java.lang.String getInterfaceDescriptor()
+                          {
+                            return DESCRIPTOR;
+                          }
+                          @Override public void Method1() throws android.os.RemoteException
+                          {
+                            android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                            android.os.Parcel _reply = android.os.Parcel.obtain();
+                            try {
+                              _data.writeInterfaceToken(DESCRIPTOR);
+                              boolean _status = mRemote.transact(Stub.TRANSACTION_Method1, _data, _reply, 0);
+                              _reply.readException();
+                            }
+                            finally {
+                              _reply.recycle();
+                              _data.recycle();
+                            }
+                          }
+                          @Override public void Method2() throws android.os.RemoteException
+                          {
+                            android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                            android.os.Parcel _reply = android.os.Parcel.obtain();
+                            try {
+                              _data.writeInterfaceToken(DESCRIPTOR);
+                              boolean _status = mRemote.transact(Stub.TRANSACTION_Method2, _data, _reply, 0);
+                              _reply.readException();
+                            }
+                            finally {
+                              _reply.recycle();
+                              _data.recycle();
+                            }
+                          }
+                        }
+                        static final int TRANSACTION_Method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
+                        /** Helper method to enforce permissions for Method1 */
+                        protected void Method1_enforcePermission() throws SecurityException {
+                          android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                          mEnforcer.enforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION, source);
+                        }
+                        static final int TRANSACTION_Method2 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
+                        /** Helper method to enforce permissions for Method2 */
+                        protected void Method2_enforcePermission() throws SecurityException {
+                          android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                          mEnforcer.enforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION, source);
+                        }
+                        /** @hide */
+                        public int getMaxTransactionId()
+                        {
+                          return 1;
+                        }
+                      }
+                      
+                      @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+                      public void Method1() throws android.os.RemoteException;
+                      @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+                      public void Method2() throws android.os.RemoteException;
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expectClean()
+    }
+
+    /* Stubs */
+
+    private val manifestPermissionStub: TestFile = java(
+        """
+        package android.Manifest;
+        class permission {
+          public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+          public static final String INTERNET = "android.permission.INTERNET";
+        }
+        """
+    ).indented()
+
+    private val enforcePermissionAnnotationStub: TestFile = java(
+        """
+        package android.annotation;
+        public @interface EnforcePermission {}
+        """
+    ).indented()
+
+    private val stubs = arrayOf(manifestPermissionStub, enforcePermissionAnnotationStub)
+}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
new file mode 100644
index 0000000..10a6e1d
--- /dev/null
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
@@ -0,0 +1,443 @@
+/*
+* Copyright (C) 2022 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+
+class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
+    override fun getDetector() = EnforcePermissionHelperDetector()
+    override fun getIssues() = listOf(
+        EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+        EnforcePermissionHelperDetector.ISSUE_MISUSING_ENFORCE_PERMISSION
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk()
+
+    fun testFirstExpressionIsFunctionCall() {
+        lint().files(
+            java(
+                """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+                        @Override
+                        @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                        public void test() throws android.os.RemoteException {
+                            Binder.getCallingUid();
+                        }
+                    }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:5: Error: Method must start with test_enforcePermission() or super.test(), if applicable [MissingEnforcePermissionHelper]
+                    @Override
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Autofix for src/Foo.java line 5: Replace with test_enforcePermission();...:
+                @@ -8 +8
+                +         test_enforcePermission();
+                +
+                """
+            )
+    }
+
+    fun testFirstExpressionIsVariableDeclaration() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        String foo = "bar";
+                        Binder.getCallingUid();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:5: Error: Method must start with test_enforcePermission() or super.test(), if applicable [MissingEnforcePermissionHelper]
+                    @Override
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Autofix for src/Foo.java line 5: Replace with test_enforcePermission();...:
+                @@ -8 +8
+                +         test_enforcePermission();
+                +
+                """
+            )
+    }
+
+    fun testMethodIsEmpty() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {}
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:5: Error: Method must start with test_enforcePermission() or super.test(), if applicable [MissingEnforcePermissionHelper]
+                    @Override
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+    }
+
+    fun testOkay() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        super.test_enforcePermission();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun testHelperWithoutSuperPrefix_Okay() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        test_enforcePermission();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun testInterfaceDefaultMethod_notStubAncestor_error() {
+        lint().files(
+            java(
+                """
+                public interface IProtected extends android.os.IInterface {
+                    @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                    default void PermissionProtected() throws android.os.RemoteException {
+                        String foo = "bar";
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/IProtected.java:2: Error: The class of PermissionProtected does not inherit from an AIDL generated Stub class [MisusingEnforcePermissionAnnotation]
+                    @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+    }
+
+    fun testInheritance_callSuper_okay() {
+        lint().files(
+            java(
+                """
+                package test;
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        super.test_enforcePermission();
+                    }
+                }
+                """
+            ).indented(),
+            java(
+                """
+                package test;
+                import test.Foo;
+                public class Bar extends Foo {
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        super.test();
+                    }
+                }
+                """
+            ).indented(),
+            java(
+                """
+                package test;
+                import test.Bar;
+                public class Baz extends Bar {
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        super.test();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun testInheritance_callHelper_okay() {
+        lint().files(
+            java(
+                """
+                package test;
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        super.test_enforcePermission();
+                    }
+                }
+                """
+            ).indented(),
+            java(
+                """
+                package test;
+                import test.Foo;
+                public class Bar extends Foo {
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        super.test();
+                    }
+                }
+                """
+            ).indented(),
+            java(
+                """
+                package test;
+                import test.Bar;
+                public class Baz extends Bar {
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        super.test_enforcePermission();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun testInheritance_missingCallInChain_error() {
+        lint().files(
+            java(
+                """
+                package test;
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        super.test_enforcePermission();
+                    }
+                }
+                """
+            ).indented(),
+            java(
+                """
+                package test;
+                import test.Foo;
+                public class Bar extends Foo {
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        doStuff();
+                    }
+                }
+                """
+            ).indented(),
+            java(
+                """
+                package test;
+                import test.Bar;
+                public class Baz extends Bar {
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        super.test();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/test/Bar.java:4: Error: Method must start with test_enforcePermission() or super.test(), if applicable [MissingEnforcePermissionHelper]
+                    @Override
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+    }
+
+    fun testInheritance_missingCall_error() {
+        lint().files(
+            java(
+                """
+                package test;
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        super.test_enforcePermission();
+                    }
+                }
+                """
+            ).indented(),
+            java(
+                """
+                package test;
+                import test.Foo;
+                public class Bar extends Foo {
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        super.test();
+                    }
+                }
+                """
+            ).indented(),
+            java(
+                """
+                package test;
+                import test.Bar;
+                public class Baz extends Bar {
+                    @Override
+                    @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                    public void test() throws android.os.RemoteException {
+                        doStuff();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/test/Baz.java:4: Error: Method must start with test_enforcePermission() or super.test(), if applicable [MissingEnforcePermissionHelper]
+                    @Override
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+    }
+
+    fun testRandomClass_notStubAncestor_error() {
+        lint().files(
+            java(
+                """
+                public class Foo {
+                    @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                    void PermissionProtected() throws android.os.RemoteException {
+                        String foo = "bar";
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:2: Error: The class of PermissionProtected does not inherit from an AIDL generated Stub class [MisusingEnforcePermissionAnnotation]
+                    @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                    ^
+                1 errors, 0 warnings
+                """
+            )
+    }
+
+    companion object {
+        val stubs = arrayOf(aidlStub, contextStub, binderStub)
+    }
+}
+
+
+
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
new file mode 100644
index 0000000..6b8e72cf
--- /dev/null
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
@@ -0,0 +1,843 @@
+/*
+ * Copyright (C) 2022 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = SimpleManualPermissionEnforcementDetector()
+    override fun getIssues(): List<Issue> = listOf(
+            SimpleManualPermissionEnforcementDetector
+            .ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk()
+
+    fun testClass() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:7: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+                @@ -5 +5
+                +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                @@ -7 +8
+                -         mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                +         test_enforcePermission();
+                """
+            )
+    }
+
+    fun testClass_orSelfFalse_warning() {
+        lint().files(
+                java(
+                    """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            mContext.enforceCallingPermission("android.permission.READ_CONTACTS", "foo");
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                    """
+                    src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            mContext.enforceCallingPermission("android.permission.READ_CONTACTS", "foo");
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                    """
+                )
+                .expectFixDiffs(
+                    """
+                    Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+                    @@ -5 +5
+                    +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                    @@ -7 +8
+                    -         mContext.enforceCallingPermission("android.permission.READ_CONTACTS", "foo");
+                    +         test_enforcePermission();
+                    """
+                )
+    }
+
+    fun testClass_enforcesFalse_warning() {
+        lint().files(
+                java(
+                    """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                    """
+                    src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                    """
+                )
+                .expectFixDiffs(
+                    """
+                    Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+                    @@ -5 +5
+                    +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                    @@ -7 +8
+                    -         mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                    +         test_enforcePermission();
+                    """
+                )
+    }
+
+    fun testAnonClass() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo {
+                    private Context mContext;
+                    private ITest itest = new ITest.Stub() {
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            mContext.enforceCallingOrSelfPermission(
+                                "android.permission.READ_CONTACTS", "foo");
+                        }
+                    };
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:8: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            mContext.enforceCallingOrSelfPermission(
+                            ^
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 8: Annotate with @EnforcePermission:
+                @@ -6 +6
+                +         @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                @@ -8 +9
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.permission.READ_CONTACTS", "foo");
+                +             test_enforcePermission();
+                """
+            )
+    }
+
+    fun testConstantEvaluation() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
+                    }
+                }
+                """
+            ).indented(),
+            *stubs,
+            manifestStub
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:8: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
+                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 8: Annotate with @EnforcePermission:
+                @@ -6 +6
+                +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                @@ -8 +9
+                -         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
+                +         test_enforcePermission();
+                """
+            )
+    }
+
+    fun testAllOf() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo {
+                    private Context mContext;
+                    private ITest itest = new ITest.Stub() {
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            mContext.enforceCallingOrSelfPermission(
+                                "android.permission.READ_CONTACTS", "foo");
+                            mContext.enforceCallingOrSelfPermission(
+                                "android.permission.WRITE_CONTACTS", "foo");
+                        }
+                    };
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:10: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            mContext.enforceCallingOrSelfPermission(
+                            ^
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
+                @@ -6 +6
+                +         @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"})
+                @@ -8 +9
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.permission.READ_CONTACTS", "foo");
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.permission.WRITE_CONTACTS", "foo");
+                +             test_enforcePermission();
+                """
+            )
+    }
+
+    fun testAllOf_mixedOrSelf_warning() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo {
+                    private Context mContext;
+                    private ITest itest = new ITest.Stub() {
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            mContext.enforceCallingOrSelfPermission(
+                                "android.permission.READ_CONTACTS", "foo");
+                            mContext.enforceCallingPermission(
+                                "android.permission.WRITE_CONTACTS", "foo");
+                        }
+                    };
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            mContext.enforceCallingPermission(
+                            ^
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
+                @@ -6 +6
+                +         @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"})
+                @@ -8 +9
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.permission.READ_CONTACTS", "foo");
+                -             mContext.enforceCallingPermission(
+                -                 "android.permission.WRITE_CONTACTS", "foo");
+                +             test_enforcePermission();
+                """
+            )
+    }
+
+    fun testAllOf_mixedEnforces_warning() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo {
+                    private Context mContext;
+                    private ITest itest = new ITest.Stub() {
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            mContext.enforceCallingOrSelfPermission(
+                                "android.permission.READ_CONTACTS", "foo");
+                            mContext.checkCallingOrSelfPermission(
+                                "android.permission.WRITE_CONTACTS", "foo");
+                        }
+                    };
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            mContext.checkCallingOrSelfPermission(
+                            ^
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
+                @@ -6 +6
+                +         @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"})
+                @@ -8 +9
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.permission.READ_CONTACTS", "foo");
+                -             mContext.checkCallingOrSelfPermission(
+                -                 "android.permission.WRITE_CONTACTS", "foo");
+                +             test_enforcePermission();
+                """
+            )
+    }
+
+    fun testPrecedingExpressions() {
+        lint().files(
+            java(
+                """
+                import android.os.Binder;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private mContext Context;
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        long uid = Binder.getCallingUid();
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun testPermissionHelper() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+
+                    @android.annotation.PermissionMethod(orSelf = true)
+                    private void helper() {
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                    }
+
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        helper();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:14: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                        helper();
+                        ~~~~~~~~~
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 14: Annotate with @EnforcePermission:
+                @@ -12 +12
+                +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                @@ -14 +15
+                -         helper();
+                +         test_enforcePermission();
+                """
+            )
+    }
+
+    fun testPermissionHelper_orSelfNotBubbledUp_warning() {
+        lint().files(
+                java(
+                    """
+                    import android.content.Context;
+                    import android.test.ITest;
+
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+
+                        @android.annotation.PermissionMethod
+                    private void helper() {
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                    }
+
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        helper();
+                    }
+                }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                    """
+                    src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            helper();
+                            ~~~~~~~~~
+                    0 errors, 1 warnings
+                    """
+                )
+                .expectFixDiffs(
+                    """
+                    Fix for src/Foo.java line 14: Annotate with @EnforcePermission:
+                    @@ -12 +12
+                    +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                    @@ -14 +15
+                    -         helper();
+                    +         test_enforcePermission();
+                    """
+                )
+    }
+
+    fun testPermissionHelperAllOf() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+
+                    @android.annotation.PermissionMethod(orSelf = true)
+                    private void helper() {
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                        mContext.enforceCallingOrSelfPermission("android.permission.WRITE_CONTACTS", "foo");
+                    }
+
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        helper();
+                        mContext.enforceCallingOrSelfPermission("FOO", "foo");
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:16: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                        mContext.enforceCallingOrSelfPermission("FOO", "foo");
+                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 16: Annotate with @EnforcePermission:
+                @@ -13 +13
+                +     @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS", "FOO"})
+                @@ -15 +16
+                -         helper();
+                -         mContext.enforceCallingOrSelfPermission("FOO", "foo");
+                +         test_enforcePermission();
+                """
+            )
+    }
+
+
+    fun testPermissionHelperNested() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+
+                    @android.annotation.PermissionMethod(orSelf = true)
+                    private void helperHelper() {
+                        helper("android.permission.WRITE_CONTACTS");
+                    }
+
+                    @android.annotation.PermissionMethod(orSelf = true)
+                    private void helper(@android.annotation.PermissionName String extraPermission) {
+                        mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+                    }
+
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        helperHelper();
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:19: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                        helperHelper();
+                        ~~~~~~~~~~~~~~~
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 19: Annotate with @EnforcePermission:
+                @@ -17 +17
+                +     @android.annotation.EnforcePermission(allOf={"android.permission.WRITE_CONTACTS", "android.permission.READ_CONTACTS"})
+                @@ -19 +20
+                -         helperHelper();
+                +         test_enforcePermission();
+                """
+            )
+    }
+
+    fun testIfExpression() {
+        lint().files(
+                java(
+                    """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            if (mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo")
+                                    != PackageManager.PERMISSION_GRANTED) {
+                                throw new SecurityException("yikes!");
+                            }
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                    """
+                    src/Foo.java:7: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            if (mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo")
+                            ^
+                    0 errors, 1 warnings
+                    """
+                )
+                .expectFixDiffs(
+                    """
+                    Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+                    @@ -5 +5
+                    +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                    @@ -7 +8
+                    -         if (mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo")
+                    -                 != PackageManager.PERMISSION_GRANTED) {
+                    -             throw new SecurityException("yikes!");
+                    -         }
+                    +         test_enforcePermission();
+                    """
+                )
+    }
+
+    fun testIfExpression_orSelfFalse_warning() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        if (mContext.checkCallingPermission("android.permission.READ_CONTACTS", "foo")
+                                != PackageManager.PERMISSION_GRANTED) {
+                            throw new SecurityException("yikes!");
+                        }
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                        if (mContext.checkCallingPermission("android.permission.READ_CONTACTS", "foo")
+                        ^
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+                @@ -5 +5
+                +     @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+                @@ -7 +8
+                -         if (mContext.checkCallingPermission("android.permission.READ_CONTACTS", "foo")
+                -                 != PackageManager.PERMISSION_GRANTED) {
+                -             throw new SecurityException("yikes!");
+                -         }
+                +         test_enforcePermission();
+                """
+            )
+    }
+
+    fun testIfExpression_otherSideEffect_ignored() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        if (mContext.checkCallingPermission("android.permission.READ_CONTACTS", "foo")
+                                != PackageManager.PERMISSION_GRANTED) {
+                            doSomethingElse();
+                            throw new SecurityException("yikes!");
+                        }
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun testIfExpression_inlinedWithSideEffect_ignored() {
+        lint().files(
+            java(
+                """
+                import android.content.Context;
+                import android.test.ITest;
+                public class Foo extends ITest.Stub {
+                    private Context mContext;
+                    @Override
+                    public void test() throws android.os.RemoteException {
+                        if (somethingElse() && mContext.checkCallingPermission("android.permission.READ_CONTACTS", "foo")
+                                != PackageManager.PERMISSION_GRANTED) {
+                            throw new SecurityException("yikes!");
+                        }
+                    }
+
+                    private boolean somethingElse() {
+                        return true;
+                    }
+                }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun testAnyOf_hardCodedAndVarArgs() {
+        lint().files(
+                java(
+                    """
+                    import android.content.Context;
+                    import android.test.ITest;
+
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+
+                        @android.annotation.PermissionMethod(anyOf = true)
+                        private void helperHelper() {
+                            helper("FOO", "BAR");
+                        }
+
+                        @android.annotation.PermissionMethod(anyOf = true, value = {"BAZ", "BUZZ"})
+                        private void helper(@android.annotation.PermissionName String... extraPermissions) {}
+
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            helperHelper();
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                    """
+                    src/Foo.java:17: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                            helperHelper();
+                            ~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                    """
+                )
+                .expectFixDiffs(
+                    """
+                    Fix for src/Foo.java line 17: Annotate with @EnforcePermission:
+                    @@ -15 +15
+                    +     @android.annotation.EnforcePermission(anyOf={"BAZ", "BUZZ", "FOO", "BAR"})
+                    @@ -17 +18
+                    -         helperHelper();
+                    +         test_enforcePermission();
+                    """
+                )
+    }
+
+
+    fun testAnyOfAllOf_mixedConsecutiveCalls_ignored() {
+        lint().files(
+                java(
+                    """
+                    import android.content.Context;
+                    import android.test.ITest;
+
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+
+                        @android.annotation.PermissionMethod
+                        private void allOfhelper() {
+                            mContext.enforceCallingOrSelfPermission("FOO");
+                            mContext.enforceCallingOrSelfPermission("BAR");
+                        }
+
+                        @android.annotation.PermissionMethod(anyOf = true, permissions = {"BAZ", "BUZZ"})
+                        private void anyOfHelper() {}
+
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            allOfhelper();
+                            anyOfHelper();
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expectClean()
+    }
+
+    fun testAnyOfAllOf_mixedNestedCalls_ignored() {
+        lint().files(
+                java(
+                    """
+                    import android.content.Context;
+                    import android.annotation.PermissionName;
+                    import android.test.ITest;
+
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+
+                        @android.annotation.PermissionMethod(anyOf = true)
+                        private void anyOfCheck(@PermissionName String... permissions) {
+                            allOfCheck("BAZ", "BUZZ");
+                        }
+
+                        @android.annotation.PermissionMethod
+                        private void allOfCheck(@PermissionName String... permissions) {}
+
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            anyOfCheck("FOO", "BAR");
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expectClean()
+    }
+
+    companion object {
+        val stubs = arrayOf(
+            aidlStub,
+            contextStub,
+            binderStub,
+            permissionMethodStub,
+            permissionNameStub
+        )
+    }
+}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
new file mode 100644
index 0000000..2ec8fdd
--- /dev/null
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
@@ -0,0 +1,88 @@
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+import com.android.tools.lint.checks.infrastructure.TestFile
+
+val aidlStub: TestFile = java(
+    """
+    package android.test;
+    public interface ITest extends android.os.IInterface {
+        public static abstract class Stub extends android.os.Binder implements android.test.ITest {
+            protected void test_enforcePermission() throws SecurityException {}
+        }
+        public void test() throws android.os.RemoteException;
+    }
+    """
+).indented()
+
+val contextStub: TestFile = java(
+    """
+    package android.content;
+    public class Context {
+        @android.annotation.PermissionMethod(orSelf = true)
+        public void enforceCallingOrSelfPermission(@android.annotation.PermissionName String permission, String message) {}
+        @android.annotation.PermissionMethod
+        public void enforceCallingPermission(@android.annotation.PermissionName String permission, String message) {}
+        @android.annotation.PermissionMethod(orSelf = true)
+        public int checkCallingOrSelfPermission(@android.annotation.PermissionName String permission, String message) {}
+        @android.annotation.PermissionMethod
+        public int checkCallingPermission(@android.annotation.PermissionName String permission, String message) {}
+    }
+    """
+).indented()
+
+val binderStub: TestFile = java(
+    """
+    package android.os;
+    public class Binder {
+        public static int getCallingUid() {}
+    }
+    """
+).indented()
+
+val permissionMethodStub: TestFile = java(
+    """
+    package android.annotation;
+
+    import static java.lang.annotation.ElementType.METHOD;
+    import static java.lang.annotation.RetentionPolicy.CLASS;
+
+    import java.lang.annotation.Retention;
+    import java.lang.annotation.Target;
+
+    @Retention(CLASS)
+    @Target({METHOD})
+    public @interface PermissionMethod {}
+    """
+).indented()
+
+val permissionNameStub: TestFile = java(
+    """
+    package android.annotation;
+
+    import static java.lang.annotation.ElementType.FIELD;
+    import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+    import static java.lang.annotation.ElementType.METHOD;
+    import static java.lang.annotation.ElementType.PARAMETER;
+    import static java.lang.annotation.RetentionPolicy.CLASS;
+
+    import java.lang.annotation.Retention;
+    import java.lang.annotation.Target;
+
+    @Retention(CLASS)
+    @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+    public @interface PermissionName {}
+    """
+).indented()
+
+val manifestStub: TestFile = java(
+    """
+    package android;
+
+    public final class Manifest {
+        public static final class permission {
+            public static final String READ_CONTACTS="android.permission.READ_CONTACTS";
+        }
+    }
+    """.trimIndent()
+)
\ No newline at end of file